Merge branch 'master' into next-merge-master

This commit is contained in:
MadCcc 2022-09-22 11:47:05 +08:00
commit eb9179464b
37 changed files with 344 additions and 266 deletions

View File

@ -15,6 +15,17 @@ timeline: true
---
## 4.23.2
`2022-09-17`
- 🐞 Fix Card console warning when using `tabList` prop. [#37537](https://github.com/ant-design/ant-design/pull/37537) [@edc-hui](https://github.com/edc-hui)
- 🐞 Fix Table `filterSearch` is not executed when `filterMode="tree"`. [#37587](https://github.com/ant-design/ant-design/pull/37587) [@edc-hui](https://github.com/edc-hui)
- 🐞 Fix Tooltip should not replace children `className` when it's not a string type. [#37598](https://github.com/ant-design/ant-design/pull/37598)
- 🐞 Fix Tree that TreeNodes not aligned when draggable and some node is disabled. [#37534](https://github.com/ant-design/ant-design/pull/37534)
- TypeScript
- 🤖 Replace deprecated `React.ReactChild` type. [#37551](https://github.com/ant-design/ant-design/pull/37551) [@bowen-wu](https://github.com/bowen-wu)
## 4.23.1
`2022-09-09`
@ -202,7 +213,7 @@ timeline: true
- 💄 Fix nested Table margin style. [#36209](https://github.com/ant-design/ant-design/pull/36209)
- 🐞 Fix Table filter dropdown with multiple subMenu may not closed. [#36132](https://github.com/ant-design/ant-design/pull/36132)
- 🐞 Table reset the last selection key when deselect or bulk operations. [#34705](https://github.com/ant-design/ant-design/pull/34705) [@Dunqing](https://github.com/Dunqing)
- 🐞 Fix Popover arrow color with custom `color` prop. [#36241](https://github.com/ant-design/ant-design/pull/36241) [@MadCcc](https://github.com/MadCcc)
- 🐞 Fix Popover arrow color with custom `color` prop. [#36241](https://github.com/ant-design/ant-design/pull/36241)
- 🐞 Fix Upload `listType="picture-card"` select button not being hidden when children is empty. [#36196](https://github.com/ant-design/ant-design/pull/36196)
- 🐞 Fix Menu deprecated warning when `item={undefined}`. [#36190](https://github.com/ant-design/ant-design/pull/36190)
- 💄 Fix Button `loading` icon margin style lost. [#36168](https://github.com/ant-design/ant-design/pull/36168)
@ -425,7 +436,7 @@ timeline: true
- Less
- 💄 Replace less html selector with related variable. [#35186](https://github.com/ant-design/ant-design/pull/35186) [@jeffdrumgod](https://github.com/jeffdrumgod)
- 💄 Modify less `danger` value from the function to variable. [#35113](https://github.com/ant-design/ant-design/pull/35113) [@TrickyPi](https://github.com/TrickyPi)
- 🐞 Arrow border radius variable use fixed value. [#35086](https://github.com/ant-design/ant-design/pull/35086) [@MadCcc](https://github.com/MadCcc)
- 🐞 Arrow border radius variable use fixed value. [#35086](https://github.com/ant-design/ant-design/pull/35086)
- TypeScript
- 🤖 Fixed `Upload` component `UploadChangeParam<T>` internal `fileList` not using generics. [#35158](https://github.com/ant-design/ant-design/pull/35158) [@rendaoer](https://github.com/rendaoer)
- 🤖 Update TypeScript definition for `@types/react@18` compatible. [#35075](https://github.com/ant-design/ant-design/pull/35075) [@AliRezaBeigy](https://github.com/AliRezaBeigy) [#35076](https://github.com/ant-design/ant-design/pull/35076) [@littledian](https://github.com/littledian)
@ -580,7 +591,7 @@ timeline: true
- 💄 Improve Menu `:focus-visible` style. [#34008](https://github.com/ant-design/ant-design/pull/34008)
- 💄 Fix Pagination and Rate style problem in Safari. [#34002](https://github.com/ant-design/ant-design/pull/34002)
- 💄 Fix Row and Col component styles when using prefixCls. [#33969](https://github.com/ant-design/ant-design/pull/33969) [@mic-web](https://github.com/mic-web)
- 🐞 Fix Timeline icons with custom color not working. [#33951](https://github.com/ant-design/ant-design/pull/33951) [@MadCcc](https://github.com/MadCcc)
- 🐞 Fix Timeline icons with custom color not working. [#33951](https://github.com/ant-design/ant-design/pull/33951)
- TypeScript
- 🤖 Optimize Cascader `onChange` definition with `multiple` prop. [#33947](https://github.com/ant-design/ant-design/pull/33947) [@babycannotsay](https://github.com/babycannotsay)
@ -663,7 +674,7 @@ timeline: true
`2021-12-29`
- 🐞 Fix Popconfirm throws `Can't perform a React state update on an unmounted component.` warning in some async case. [#33432](https://github.com/ant-design/ant-design/pull/33432) [@MadCcc](https://github.com/MadCcc)
- 🐞 Fix Popconfirm throws `Can't perform a React state update on an unmounted component.` warning in some async case. [#33432](https://github.com/ant-design/ant-design/pull/33432)
- 🐞 Fix Input with `suffix` will crash when `value` is number type. [#33462](https://github.com/ant-design/ant-design/pull/33462)
- 🐞 Fix Divider with text dashed border color error. [#33452](https://github.com/ant-design/ant-design/pull/33452) [@chen-jingjie](https://github.com/chen-jingjie)
- 🐞 Fix Dropdown.Button not support `destroyPopupOnHide`. [#33442](https://github.com/ant-design/ant-design/pull/33442) [@LongHaoo](https://github.com/LongHaoo)

View File

@ -15,6 +15,17 @@ timeline: true
---
## 4.23.2
`2022-09-17`
- 🐞 修复 Card 传入 `tabList` 属性时控制台出现废弃警告的问题。[#37537](https://github.com/ant-design/ant-design/pull/37537) [@edc-hui](https://github.com/edc-hui)
- 🐞 修复 Table `filterMode="tree"``filterSearch` 函数未执行的问题。[#37587](https://github.com/ant-design/ant-design/pull/37587) [@edc-hui](https://github.com/edc-hui)
- 🐞 修复 Tooltip 的子元素 `className` 非 string 类型时会被覆盖的问题。[#37598](https://github.com/ant-design/ant-design/pull/37598)
- 🐞 修复 Tree 组件 TreeNode 在可拖拽并且禁用状态下不对齐的问题。[#37534](https://github.com/ant-design/ant-design/pull/37534)
- TypeScript
- 🤖 替换已废弃的 `React.ReactChild` 定义。[#37551](https://github.com/ant-design/ant-design/pull/37551) [@bowen-wu](https://github.com/bowen-wu)
## 4.23.1
`2022-09-09`
@ -203,7 +214,7 @@ timeline: true
- 🐞 Table 取消选择或批量操作时重置上一次选择的 key。[#34705](https://github.com/ant-design/ant-design/pull/34705) [@Dunqing](https://github.com/Dunqing)
- 🐞 修复 Table 过滤列表在某些场景下多级展开无法关闭的问题。[#36132](https://github.com/ant-design/ant-design/pull/36132)
- 🐞 修复 Upload `listType="picture-card"` 当 children 为空时上传文件按钮没有隐藏的问题。[#36196](https://github.com/ant-design/ant-design/pull/36196)
- 🐞 修复 Popover 自定义 `color` 时箭头颜色问题。[#36241](https://github.com/ant-design/ant-design/pull/36241) [@MadCcc](https://github.com/MadCcc)
- 🐞 修复 Popover 自定义 `color` 时箭头颜色问题。[#36241](https://github.com/ant-design/ant-design/pull/36241)
- 🐞 修复 Menu `item={undefined}` 时会有废弃警告的问题。[#36190](https://github.com/ant-design/ant-design/pull/36190)
- 💄 修复 Button `loading` 图标的间距丢失的问题。[#36168](https://github.com/ant-design/ant-design/pull/36168)
- 🐞 修复 Dropdown 中 Menu 分组下的 Item 点击不会关闭的问题。[#36148](https://github.com/ant-design/ant-design/pull/36148)
@ -421,12 +432,12 @@ timeline: true
- 🐞 修复 Title、Text、Paragraph 组件不支持 `ref` 的问题。[#34847](https://github.com/ant-design/ant-design/pull/34847) [@MQuy](https://github.com/MQuy)
- Input
- 💄 Input.Group 对子组件屏蔽 Form.Item 的样式。[#34764](https://github.com/ant-design/ant-design/pull/34764)
- 💄 调整 Form 下 TextArea 的样式。[#34714](https://github.com/ant-design/ant-design/pull/34714) [@MadCcc](https://github.com/MadCcc)
- 💄 调整 Form 下 TextArea 的样式。[#34714](https://github.com/ant-design/ant-design/pull/34714)
- ⌨️ 修复 Checkbox 缺少 `aria-checked` 属性导致屏幕阅读器识别错误的问题。[#34862](https://github.com/ant-design/ant-design/pull/34862) [@SpaNb4](https://github.com/SpaNb4)
- Less
- 💄 替换 less 中的 html 选择器为对应变量。[#35186](https://github.com/ant-design/ant-design/pull/35186) [@jeffdrumgod](https://github.com/jeffdrumgod)
- 💄 修改 less 中 `danger` 值从函数改为变量。[#35113](https://github.com/ant-design/ant-design/pull/35113) [@TrickyPi](https://github.com/TrickyPi)
- 🐞 箭头圆角使用固定值 2px 变量。[#35086](https://github.com/ant-design/ant-design/pull/35086) [@MadCcc](https://github.com/MadCcc)
- 🐞 箭头圆角使用固定值 2px 变量。[#35086](https://github.com/ant-design/ant-design/pull/35086)
- TypeScript
- 🤖 修正 Upload 组件中 `UploadChangeParam<T>` 内部 `fileList` 不使用泛型问题。[#35158](https://github.com/ant-design/ant-design/pull/35158) [@rendaoer](https://github.com/rendaoer)
- 🤖 更新 TypeScript 定义以兼容 `@types/react@18`。[#35075](https://github.com/ant-design/ant-design/pull/35075) [@AliRezaBeigy](https://github.com/AliRezaBeigy) [#35076](https://github.com/ant-design/ant-design/pull/35076) [@littledian](https://github.com/littledian)
@ -582,7 +593,7 @@ timeline: true
- 💄 优化 Menu `:focus-visible` 的样式。[#34008](https://github.com/ant-design/ant-design/pull/34008)
- 💄 修复 Pagination 和 Rate 在 Safari 下部分样式丢失的问题,比如分页按钮禁用样式失效。[#34002](https://github.com/ant-design/ant-design/pull/34002)
- 💄 修复 Row 与 Col 在配置 `prefixCls` 的样式问题。[#33969](https://github.com/ant-design/ant-design/pull/33969) [@mic-web](https://github.com/mic-web)
- 🐞 修复 Timeline 的自定义图标颜色无效的问题。[#33951](https://github.com/ant-design/ant-design/pull/33951) [@MadCcc](https://github.com/MadCcc)
- 🐞 修复 Timeline 的自定义图标颜色无效的问题。[#33951](https://github.com/ant-design/ant-design/pull/33951)
- TypeScript
- 🤖 优化 Cascader `multiple` 属性对应的 `onChange` 类型推断。[#33947](https://github.com/ant-design/ant-design/pull/33947) [@babycannotsay](https://github.com/babycannotsay)
@ -665,7 +676,7 @@ timeline: true
`2021-12-29`
- 🐞 修复 Popconfirm 在某些情况下会出现 `Can't perform a React state update on an unmounted component.` 的错误。[#33432](https://github.com/ant-design/ant-design/pull/33432) [@MadCcc](https://github.com/MadCcc)
- 🐞 修复 Popconfirm 在某些情况下会出现 `Can't perform a React state update on an unmounted component.` 的错误。[#33432](https://github.com/ant-design/ant-design/pull/33432)
- 🐞 修复 Input 配置 `suffix``value` 为数字类型会崩溃的问题。[#33462](https://github.com/ant-design/ant-design/pull/33462)
- 🐞 修复 Divider with text dashed 的边框颜色错误问题。[#33452](https://github.com/ant-design/ant-design/pull/33452) [@chen-jingjie](https://github.com/chen-jingjie)
- 🐞 修复 Dropdown.Button 不支持 `destroyPopupOnHide` 的问题。[#33442](https://github.com/ant-design/ant-design/pull/33442) [@LongHaoo](https://github.com/LongHaoo)

View File

@ -1,6 +1,6 @@
import React from 'react';
import mountTest from '../../../tests/shared/mountTest';
import { render, sleep, fireEvent } from '../../../tests/utils';
import { render, sleep, fireEvent, act } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import Wave from '../wave';
@ -270,4 +270,31 @@ describe('Wave component', () => {
expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: red;');
unmount();
});
it('Wave style should append to validate element', () => {
jest.useFakeTimers();
const { container } = render(
<Wave>
<div className="bamboo" style={{ borderColor: 'red' }} />
</Wave>,
);
// Mock shadow container
const fakeDoc = document.createElement('div');
fakeDoc.append('text');
fakeDoc.appendChild(document.createElement('span'));
expect(fakeDoc.childNodes).toHaveLength(2);
(container.querySelector('.bamboo') as any).getRootNode = () => fakeDoc;
// Click should not throw
fireEvent.click(container.querySelector('.bamboo')!);
act(() => {
jest.runAllTimers();
});
expect(fakeDoc.querySelector('style')).toBeTruthy();
jest.useRealTimers();
});
});

View File

@ -18,6 +18,16 @@ function isHidden(element: HTMLElement) {
return !element || element.offsetParent === null || element.hidden;
}
function getValidateContainer(nodeRoot: Node): Element {
if (nodeRoot instanceof Document) {
return nodeRoot.body;
}
return Array.from(nodeRoot.childNodes).find(
ele => ele?.nodeType === Node.ELEMENT_NODE,
) as Element;
}
function isNotGrey(color: string) {
// eslint-disable-next-line no-useless-escape
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/);
@ -119,8 +129,7 @@ class InternalWave extends React.Component<WaveProps> {
extraNode.style.borderColor = waveColor;
const nodeRoot = node.getRootNode?.() || node.ownerDocument;
const nodeBody: Element =
nodeRoot instanceof Document ? nodeRoot.body : (nodeRoot.firstChild as Element) ?? nodeRoot;
const nodeBody = getValidateContainer(nodeRoot) ?? nodeRoot;
styleForPseudo = updateCSS(
`

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Alert action of Alert custom action 1`] = `
exports[`Alert custom action 1`] = `
<div
class="ant-alert ant-alert-success"
data-show="true"
@ -74,29 +74,6 @@ exports[`Alert action of Alert custom action 1`] = `
</div>
`;
exports[`Alert could accept none react element icon 1`] = `
<div
class="ant-alert ant-alert-success"
data-show="true"
role="alert"
>
<span
class="ant-alert-icon"
>
icon
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Success Tips
</div>
</div>
</div>
`;
exports[`Alert rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-alert ant-alert-info ant-alert-no-icon ant-alert-rtl"
@ -108,30 +85,3 @@ exports[`Alert rtl render component should be rendered correctly in RTL directio
/>
</div>
`;
exports[`Alert support closeIcon 1`] = `
<div
class="ant-alert ant-alert-warning ant-alert-no-icon"
data-show="true"
role="alert"
>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text
</div>
</div>
<button
class="ant-alert-close-icon"
tabindex="0"
type="button"
>
<span>
close
</span>
</button>
</div>
`;

View File

@ -1,8 +1,9 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import Alert from '..';
import accessibilityTest from '../../../tests/shared/accessibilityTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep, act } from '../../../tests/utils';
import { render, act, screen } from '../../../tests/utils';
import Button from '../../button';
import Popconfirm from '../../popconfirm';
import Tooltip from '../../tooltip';
@ -21,9 +22,9 @@ describe('Alert', () => {
jest.useRealTimers();
});
it('could be closed', () => {
it('should show close button and could be closed', async () => {
const onClose = jest.fn();
const { container } = render(
render(
<Alert
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning"
@ -32,125 +33,102 @@ describe('Alert', () => {
/>,
);
jest.useFakeTimers();
fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
await userEvent.click(screen.getByRole('button', { name: /close/i }));
act(() => {
jest.runAllTimers();
});
expect(onClose).toHaveBeenCalled();
jest.useRealTimers();
expect(onClose).toHaveBeenCalledTimes(1);
});
describe('action of Alert', () => {
it('custom action', () => {
const { container } = render(
<Alert
message="Success Tips"
type="success"
showIcon
action={
<Button size="small" type="text">
UNDO
</Button>
}
closable
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});
it('support closeIcon', () => {
it('custom action', () => {
const { container } = render(
<Alert
message="Success Tips"
type="success"
showIcon
action={
<Button size="small" type="text">
UNDO
</Button>
}
closable
closeIcon={<span>close</span>}
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning"
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
describe('data and aria props', () => {
it('sets data attributes on input', () => {
const { container } = render(<Alert data-test="test-id" data-id="12345" message={null} />);
const input = container.querySelector('.ant-alert')!;
expect(input.getAttribute('data-test')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
});
it('sets aria attributes on input', () => {
const { container } = render(<Alert aria-describedby="some-label" message={null} />);
const input = container.querySelector('.ant-alert')!;
expect(input.getAttribute('aria-describedby')).toBe('some-label');
});
it('sets role attribute on input', () => {
const { container } = render(<Alert role="status" message={null} />);
const input = container.querySelector('.ant-alert')!;
expect(input.getAttribute('role')).toBe('status');
});
it('should sets data attributes on alert when pass attributes to props', () => {
render(
<Alert data-test="test-id" data-id="12345" aria-describedby="some-label" message={null} />,
);
const alert = screen.getByRole('alert');
expect(alert).toHaveAttribute('data-test', 'test-id');
expect(alert).toHaveAttribute('data-id', '12345');
expect(alert).toHaveAttribute('aria-describedby', 'some-label');
});
it('ErrorBoundary', () => {
it('sets role attribute on input', () => {
render(<Alert role="status" message={null} />);
expect(screen.getByRole('status')).toBeInTheDocument();
});
it('should show error as ErrorBoundary when children have error', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledTimes(0);
// @ts-expect-error
// eslint-disable-next-line react/jsx-no-undef
const ThrowError = () => <NotExisted />;
const { container } = render(
render(
<ErrorBoundary>
<ThrowError />
</ErrorBoundary>,
);
// eslint-disable-next-line jest/no-standalone-expect
expect(container.textContent).toContain('ReferenceError: NotExisted is not defined');
expect(screen.getByRole('alert')).toHaveTextContent(
'ReferenceError: NotExisted is not defined',
);
// eslint-disable-next-line no-console
(console.error as any).mockRestore();
});
it('could be used with Tooltip', async () => {
const ref = React.createRef<any>();
jest.useRealTimers();
const { container } = render(
<Tooltip title="xxx" mouseEnterDelay={0} ref={ref}>
render(
<Tooltip title="xxx" mouseEnterDelay={0}>
<Alert
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning"
/>
</Tooltip>,
);
// wrapper.find('.ant-alert').simulate('mouseenter');
fireEvent.mouseEnter(container.querySelector('.ant-alert')!);
await sleep(0);
expect(ref.current.getPopupDomNode()).toBeTruthy();
jest.useFakeTimers();
await userEvent.hover(screen.getByRole('alert'));
expect(screen.getByRole('tooltip')).toBeInTheDocument();
});
it('could be used with Popconfirm', async () => {
const ref = React.createRef<any>();
jest.useRealTimers();
const { container } = render(
<Popconfirm ref={ref} title="xxx">
render(
<Popconfirm title="xxx">
<Alert
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning"
/>
</Popconfirm>,
);
fireEvent.click(container.querySelector('.ant-alert')!);
await sleep(0);
expect(ref.current.getPopupDomNode()).toBeTruthy();
jest.useFakeTimers();
await userEvent.click(screen.getByRole('alert'));
expect(screen.getByRole('tooltip')).toBeInTheDocument();
});
it('could accept none react element icon', () => {
const { container } = render(
<Alert message="Success Tips" type="success" showIcon icon="icon" />,
);
expect(container.firstChild).toMatchSnapshot();
render(<Alert message="Success Tips" type="success" showIcon icon="icon" />);
expect(screen.getByRole('alert')).toHaveTextContent(/success tips/i);
expect(screen.getByRole('alert')).toHaveTextContent(/icon/i);
});
it('should not render message div when no message', () => {

View File

@ -150,6 +150,8 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
}
componentDidUpdate() {
const { getCurrentAnchor } = this.props;
const { activeLink } = this.state;
if (this.scrollEvent) {
const currentContainer = this.getContainer();
if (this.scrollContainer !== currentContainer) {
@ -159,6 +161,9 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
this.handleScroll();
}
}
if (typeof getCurrentAnchor === 'function') {
this.setCurrentActiveLink(getCurrentAnchor(activeLink || ''), false);
}
this.updateInk();
}
@ -227,7 +232,7 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
this.inkNode = node;
};
setCurrentActiveLink = (link: string) => {
setCurrentActiveLink = (link: string, triggerChange = true) => {
const { activeLink } = this.state;
const { onChange, getCurrentAnchor } = this.props;
if (activeLink === link) {
@ -237,7 +242,9 @@ class Anchor extends React.Component<InternalAnchorProps, AnchorState, ConfigCon
this.setState({
activeLink: typeof getCurrentAnchor === 'function' ? getCurrentAnchor(link) : link,
});
onChange?.(link);
if (triggerChange) {
onChange?.(link);
}
};
handleScroll = () => {

View File

@ -736,5 +736,21 @@ describe('Anchor Render', () => {
fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!);
expect(getCurrentAnchor).toHaveBeenCalledWith(`#${hash2}`);
});
// https://github.com/ant-design/ant-design/issues/37627
it('should update anchorLink when component is rerender', async () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const Demo: React.FC<{ current: string }> = ({ current }) => (
<Anchor getCurrentAnchor={() => `#${current}`}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>
);
const { container, rerender } = render(<Demo current={hash1} />);
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash1);
rerender(<Demo current={hash2} />);
expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash2);
});
});
});

View File

@ -121,11 +121,12 @@ const Card = React.forwardRef((props: CardProps, ref: React.Ref<HTMLDivElement>)
{...extraProps}
className={`${prefixCls}-head-tabs`}
onChange={onTabChange}
>
{tabList.map(item => (
<Tabs.TabPane tab={item.tab} disabled={item.disabled} key={item.key} />
))}
</Tabs>
items={tabList.map(item => ({
label: item.tab,
key: item.key,
disabled: item.disabled ?? false,
}))}
/>
) : null;
if (title || extra || tabs) {
head = (

View File

@ -767,6 +767,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-tabtest"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-tabtest"
@ -781,6 +782,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-tabtest"
aria-disabled="false"
aria-selected="false"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-tabtest"
@ -907,6 +909,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-article"
aria-disabled="false"
aria-selected="false"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-article"
@ -921,6 +924,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-app"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-app"
@ -935,6 +939,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-project"
aria-disabled="false"
aria-selected="false"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-project"

View File

@ -767,6 +767,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-tabtest"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-tabtest"
@ -781,6 +782,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-tabtest"
aria-disabled="false"
aria-selected="false"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-tabtest"
@ -888,6 +890,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-article"
aria-disabled="false"
aria-selected="false"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-article"
@ -902,6 +905,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-app"
aria-disabled="false"
aria-selected="true"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-app"
@ -916,6 +920,7 @@ Array [
>
<div
aria-controls="rc-tabs-test-panel-project"
aria-disabled="false"
aria-selected="false"
class="ant-tabs-tab-btn"
id="rc-tabs-test-tab-project"

View File

@ -6,8 +6,6 @@ import Button from '../../button/index';
import Card from '../index';
import '@testing-library/jest-dom';
console.log('fireEvent');
describe('Card', () => {
mountTest(Card);
rtlTest(Card);

View File

@ -12,6 +12,7 @@ describe('Icon', () => {
it('should throw Error', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Icon />);
expect(errSpy).toHaveBeenCalled();
expect(errSpy).toHaveBeenCalledWith('Warning: [antd: Icon] Empty Icon');
errSpy.mockRestore();
});
});

View File

@ -31,6 +31,8 @@ const localeValues: Locale = {
searchPlaceholder: 'Procurar...',
itemUnit: 'item',
itemsUnit: 'itens',
selectAll: 'Seleccionar Tudo',
selectInvert: 'Inverter a página actual',
},
Upload: {
uploading: 'A carregar...',

View File

@ -132,6 +132,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
close,
zIndex,
afterClose,
visible,
open,
keyboard,
centered,
@ -149,6 +150,14 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
focusTriggerAfterClose,
} = props;
if (process.env.NODE_ENV !== 'production') {
warning(
visible === undefined,
'Modal',
`\`visible\` is deprecated, please use \`open\` instead.`,
);
}
const confirmPrefixCls = `${prefixCls}-confirm`;
const width = props.width || 416;

View File

@ -44,14 +44,7 @@ export default function confirm(config: ModalFuncProps) {
reactUnmount(container);
}
function render({
okText,
cancelText,
prefixCls: customizePrefixCls,
open,
visible,
...props
}: any) {
function render({ okText, cancelText, prefixCls: customizePrefixCls, ...props }: any) {
/**
* https://github.com/ant-design/ant-design/issues/23623
*
@ -68,7 +61,6 @@ export default function confirm(config: ModalFuncProps) {
reactRender(
<ConfirmDialog
{...props}
open={open ?? visible}
prefixCls={prefixCls}
rootPrefixCls={rootPrefixCls}
iconPrefixCls={iconPrefixCls}
@ -93,6 +85,12 @@ export default function confirm(config: ModalFuncProps) {
destroy.apply(this, args);
},
};
// Legacy support
if (currentConfig.visible) {
delete currentConfig.visible;
}
render(currentConfig);
}

View File

@ -311,7 +311,7 @@ function InternalTable<RecordType extends object = any>(
);
};
const [transformFilterColumns, filterStates, getFilters] = useFilter<RecordType>({
const [transformFilterColumns, filterStates, filters] = useFilter<RecordType>({
prefixCls,
locale: tableLocale,
dropdownPrefixCls,
@ -321,16 +321,23 @@ function InternalTable<RecordType extends object = any>(
});
const mergedData = getFilterData(sortedData, filterStates);
changeEventInfo.filters = getFilters();
changeEventInfo.filters = filters;
changeEventInfo.filterStates = filterStates;
// ============================ Column ============================
const columnTitleProps = React.useMemo(
() => ({
const columnTitleProps = React.useMemo(() => {
const mergedFilters: Record<string, FilterValue> = {};
Object.keys(filters).forEach(filterKey => {
if (filters[filterKey] !== null) {
mergedFilters[filterKey] = filters[filterKey]!;
}
});
return {
...sorterTitleProps,
}),
[sorterTitleProps],
);
filters: mergedFilters,
};
}, [sorterTitleProps, filters]);
const [transformTitleColumns] = useTitleColumns(columnTitleProps);
// ========================== Pagination ==========================

View File

@ -13,6 +13,7 @@ import type { SelectProps } from '../../select';
import type { ColumnGroupType, ColumnType, TableProps } from '..';
import type { ColumnFilterItem, FilterDropdownProps, FilterValue } from '../interface';
import { resetWarned } from '../../_util/warning';
import type { TreeColumnFilterItem } from '../hooks/useFilter/FilterDropdown';
// https://github.com/Semantic-Org/Semantic-UI-React/blob/72c45080e4f20b531fda2e3e430e384083d6766b/test/specs/modules/Dropdown/Dropdown-test.js#L73
const nativeEvent = { nativeEvent: { stopImmediatePropagation: () => {} } };
@ -1950,6 +1951,35 @@ describe('Table.filter', () => {
expect(container.querySelectorAll('li.ant-dropdown-menu-item').length).toBe(2);
});
it('should supports filterSearch has type of function when filterMode is tree', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
const { container } = render(
createTable({
columns: [
{
...column,
filterMode: 'tree',
filters: [
{ text: '节点一', value: 'node1' },
{ text: '节点二', value: 'node2' },
{ text: '节点三', value: 'node3' },
],
filterSearch: (input: any, record: TreeColumnFilterItem) =>
(record.title as string).includes(input),
},
],
}),
);
fireEvent.click(container.querySelector('span.ant-dropdown-trigger')!, nativeEvent);
act(() => {
jest.runAllTimers();
});
expect(container.querySelectorAll('.ant-table-filter-dropdown-tree').length).toBe(1);
expect(container.querySelectorAll('.ant-input').length).toBe(1);
fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '节点二' } });
expect(container.querySelectorAll('.ant-tree-treenode.filter-node').length).toBe(1);
});
it('supports check all items', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
const { container } = render(
@ -2457,4 +2487,26 @@ describe('Table.filter', () => {
?.disabled,
).toBeTruthy();
});
it('title render function support `filter`', () => {
const title = jest.fn(() => 'RenderTitle');
const { container } = render(
createTable({
columns: [
{
...column,
title,
filteredValue: ['boy'],
},
],
}),
);
expect(container.querySelector('.ant-table-column-title')?.textContent).toEqual('RenderTitle');
expect(title).toHaveBeenCalledWith(
expect.objectContaining({
filters: { name: ['boy'] },
}),
);
});
});

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import type { ColumnProps } from '..';
import type { TreeColumnFilterItem } from '../hooks/useFilter/FilterDropdown';
import Table from '../Table';
const { Column, ColumnGroup } = Table;
@ -48,6 +49,8 @@ describe('Table.typescript types', () => {
{
title: 'Name',
dataIndex: 'name',
filterSearch: (input: any, record: TreeColumnFilterItem) =>
(record.title as string).includes(input),
},
];

View File

@ -61,7 +61,7 @@ function renderFilterItems({
filteredKeys: Key[];
filterMultiple: boolean;
searchValue: string;
filterSearch: FilterSearchType;
filterSearch: FilterSearchType<ColumnFilterItem>;
}): Required<MenuProps>['items'] {
return filters.map((filter, index) => {
const key = String(filter.value);
@ -103,6 +103,8 @@ function renderFilterItems({
});
}
export type TreeColumnFilterItem = ColumnFilterItem & FilterTreeDataNode;
export interface FilterDropdownProps<RecordType> {
tablePrefixCls: string;
prefixCls: string;
@ -111,7 +113,7 @@ export interface FilterDropdownProps<RecordType> {
filterState?: FilterState<RecordType>;
filterMultiple: boolean;
filterMode?: 'menu' | 'tree';
filterSearch?: FilterSearchType;
filterSearch?: FilterSearchType<ColumnFilterItem | TreeColumnFilterItem>;
columnKey: Key;
children: React.ReactNode;
triggerFilter: (filterState: FilterState<RecordType>) => void;
@ -312,6 +314,12 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
}
return item;
});
const getFilterData = (node: FilterTreeDataNode): TreeColumnFilterItem => ({
...node,
text: node.title,
value: node.key,
children: node.children?.map(item => getFilterData(item)) || [],
});
let dropdownContent: React.ReactNode;
@ -348,7 +356,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
if (filterMode === 'tree') {
return (
<>
<FilterSearch
<FilterSearch<TreeColumnFilterItem>
filterSearch={filterSearch}
value={searchValue}
onChange={onSearch}
@ -385,7 +393,12 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
defaultExpandAll
filterTreeNode={
searchValue.trim()
? node => searchValueMatched(searchValue, node.title)
? node => {
if (typeof filterSearch === 'function') {
return filterSearch(searchValue, getFilterData(node));
}
return searchValueMatched(searchValue, node.title);
}
: undefined
}
/>

View File

@ -3,21 +3,21 @@ import * as React from 'react';
import Input from '../../../input';
import type { FilterSearchType, TableLocale } from '../../interface';
interface FilterSearchProps {
interface FilterSearchProps<RecordType = any> {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
filterSearch: FilterSearchType;
filterSearch: FilterSearchType<RecordType>;
tablePrefixCls: string;
locale: TableLocale;
}
const FilterSearch: React.FC<FilterSearchProps> = ({
function FilterSearch<RecordType>({
value,
onChange,
filterSearch,
tablePrefixCls,
locale,
}) => {
}: FilterSearchProps<RecordType>) {
if (!filterSearch) {
return null;
}
@ -34,6 +34,6 @@ const FilterSearch: React.FC<FilterSearchProps> = ({
/>
</div>
);
};
}
export default FilterSearch;

View File

@ -203,7 +203,7 @@ function useFilter<RecordType>({
}: FilterConfig<RecordType>): [
TransformColumns<RecordType>,
FilterState<RecordType>[],
() => Record<string, FilterValue | null>,
Record<string, FilterValue | null>,
] {
const [filterStates, setFilterStates] = React.useState<FilterState<RecordType>[]>(
collectFilterStates(mergedColumns, true),
@ -235,10 +235,7 @@ function useFilter<RecordType>({
return collectedStates;
}, [mergedColumns, filterStates]);
const getFilters = React.useCallback(
() => generateFilterInfo(mergedFilterStates),
[mergedFilterStates],
);
const filters = React.useMemo(() => generateFilterInfo(mergedFilterStates), [mergedFilterStates]);
const triggerFilter = (filterState: FilterState<RecordType>) => {
const newFilterStates = mergedFilterStates.filter(({ key }) => key !== filterState.key);
@ -258,7 +255,7 @@ function useFilter<RecordType>({
tableLocale,
);
return [transformColumns, mergedFilterStates, getFilters];
return [transformColumns, mergedFilterStates, filters];
}
export default useFilter;

View File

@ -63,7 +63,7 @@ export interface ColumnTitleProps<RecordType> {
sortColumn?: ColumnType<RecordType>;
sortColumns?: { column: ColumnType<RecordType>; order: SortOrder }[];
filters?: Record<string, string[]>;
filters?: Record<string, FilterValue>;
}
export type ColumnTitle<RecordType> =
@ -72,7 +72,9 @@ export type ColumnTitle<RecordType> =
export type FilterValue = (Key | boolean)[];
export type FilterKey = Key[] | null;
export type FilterSearchType = boolean | ((input: string, record: {}) => boolean);
export type FilterSearchType<RecordType = Record<string, any>> =
| boolean
| ((input: string, record: RecordType) => boolean);
export interface FilterConfirmProps {
closeDropdown: boolean;
}
@ -112,7 +114,7 @@ export interface ColumnType<RecordType> extends Omit<RcColumnType<RecordType>, '
defaultFilteredValue?: FilterValue | null;
filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode);
filterMode?: 'menu' | 'tree';
filterSearch?: FilterSearchType;
filterSearch?: FilterSearchType<ColumnFilterItem>;
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
filterDropdownOpen?: boolean;
onFilterDropdownOpenChange?: (visible: boolean) => void;

View File

@ -14,10 +14,12 @@ describe('Tabs.Animated', () => {
it('boolean: true', () => {
const { result } = renderHook(() => useAnimateConfig('test', true));
expect(result.current).toEqual({
inkBar: true,
tabPane: false,
});
expect(result.current).toEqual(
expect.objectContaining({
inkBar: true,
tabPane: true,
}),
);
});
it('config', () => {

View File

@ -26,7 +26,7 @@ export default function useAnimateConfig(
} else if (animated === true) {
mergedAnimated = {
inkBar: true,
tabPane: false,
tabPane: true,
};
} else {
mergedAnimated = {

View File

@ -845,6 +845,7 @@ const genTabsStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
},
[`${componentCls}-tabpane`]: {
outline: 'none',
'&-hidden': {
display: 'none',
},

View File

@ -538,4 +538,16 @@ describe('Tooltip', () => {
jest.useRealTimers();
errSpy.mockRestore();
});
it('not inject className when children className is not string type', () => {
const HOC = ({ className }: { className: Function }) => <span className={className()} />;
const { container } = render(
<Tooltip open>
<HOC className={() => 'bamboo'} />
</Tooltip>,
);
expect(container.querySelector('.bamboo')).toBeTruthy();
expect(container.querySelector('.ant-tooltip')).toBeTruthy();
});
});

View File

@ -30,7 +30,7 @@ The following APIs are shared by Tooltip, Popconfirm, Popover.
| color | The background color | string | - | 4.3.0 |
| defaultOpen | Whether the floating tooltip card is open by default | boolean | false | 4.23.0 |
| destroyTooltipOnHide | Whether destroy tooltip when hidden, parent container of tooltip will be destroyed when `keepParent` is false | boolean \| { keepParent?: boolean } | false | |
| getPopupContainer | The DOM container of the tip, the default behavior is to create a `div` element in `body` | function(triggerNode) | () => document.body | |
| getPopupContainer | The DOM container of the tip, the default behavior is to create a `div` element in `body` | (triggerNode: HTMLElement) => HTMLElement | () => document.body | |
| mouseEnterDelay | Delay in seconds, before tooltip is shown on mouse enter | number | 0.1 | |
| mouseLeaveDelay | Delay in seconds, before tooltip is hidden on mouse leave | number | 0.1 | |
| overlayClassName | Class name of the tooltip card | string | - | |
@ -40,7 +40,7 @@ The following APIs are shared by Tooltip, Popconfirm, Popover.
| trigger | Tooltip trigger mode. Could be multiple by passing an array | `hover` \| `focus` \| `click` \| `contextMenu` \| Array&lt;string> | `hover` | |
| open | Whether the floating tooltip card is open or not. Use `visible` under 4.23.0 ([why?](/docs/react/faq#why-open)) | boolean | false | 4.23.0 |
| zIndex | Config `z-index` of Tooltip | number | - | |
| onOpenChange | Callback executed when visibility of the tooltip card is changed | (open) => void | - | 4.23.0 |
| onOpenChange | Callback executed when visibility of the tooltip card is changed | (open: boolean) => void | - | 4.23.0 |
## Note

View File

@ -276,9 +276,12 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
prefixCls,
);
const childProps = child.props;
const childCls = classNames(childProps.className, {
[openClassName || `${prefixCls}-open`]: true,
});
const childCls =
!childProps.className || typeof childProps.className === 'string'
? classNames(childProps.className, {
[openClassName || `${prefixCls}-open`]: true,
})
: childProps.className;
// Style
const [wrapSSR, hashId] = useStyle(prefixCls, !injectFromPopover);

View File

@ -32,7 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Vyyeu8jq2/Tooltp.svg
| color | 背景颜色 | string | - | 4.3.0 |
| defaultOpen | 默认是否显隐 | boolean | false | 4.23.0 |
| destroyTooltipOnHide | 关闭后是否销毁 Tooltip`keepParent``false` 时销毁父容器 | boolean \| { keepParent?: boolean } | false | |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | function(triggerNode) | () => document.body | |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | (triggerNode: HTMLElement) => HTMLElement | () => document.body | |
| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 | |
| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip单位秒 | number | 0.1 | |
| overlayClassName | 卡片类名 | string | - | |
@ -42,7 +42,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Vyyeu8jq2/Tooltp.svg
| trigger | 触发行为,可选 `hover` \| `focus` \| `click` \| `contextMenu`,可使用数组设置多个触发行为 | string \| string\[] | `hover` | |
| open | 用于手动控制浮层显隐,小于 4.23.0 使用 `visible`[为什么?](/docs/react/faq#why-open) | boolean | false | 4.23.0 |
| zIndex | 设置 Tooltip 的 `z-index` | number | - | |
| onOpenChange | 显示隐藏的回调 | (open) => void | - | 4.23.0 |
| onOpenChange | 显示隐藏的回调 | (open: boolean) => void | - | 4.23.0 |
## 注意

View File

@ -24,7 +24,7 @@ Basic text writing, including headings, body text, lists, and more.
| delete | Deleted line style | boolean | false | |
| disabled | Disabled content | boolean | false | |
| editable | If editable. Can control edit state when is object | boolean \| [editable](#editable) | false | [editable](#editable) |
| ellipsis | Display ellipsis when text overflowscan't configure expandable、rows and onExpand by using object | boolean \| [Omit<ellipsis, 'expandable' \| 'rows' \| 'onExpand'>](#ellipsis) | false | [ellipsis](#ellipsis) |
| ellipsis | Display ellipsis when text overflowscan't configure expandable、rows and onExpand by using object. Diff with Typography.Paragraph, Text do not have 100% width style which means it will fix width on the first ellipsis. If you want to have responsive ellipsis, please set width manually | boolean \| [Omit<ellipsis, 'expandable' \| 'rows' \| 'onExpand'>](#ellipsis) | false | [ellipsis](#ellipsis) |
| keyboard | Keyboard style | boolean | false | 4.3.0 |
| mark | Marked style | boolean | false | |
| onClick | Set the handler to handle click event | (event) => void | - | |

View File

@ -25,7 +25,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
| delete | 添加删除线样式 | boolean | false | |
| disabled | 禁用文本 | boolean | false | |
| editable | 是否可编辑,为对象时可对编辑进行控制 | boolean \| [editable](#editable) | false | [editable](#editable) |
| ellipsis | 自动溢出省略为对象时不能设置省略行数、是否可展开、onExpand 展开事件 | boolean \| [Omit<ellipsis, 'expandable' \| 'rows' \| 'onExpand'>](#ellipsis) | false | [ellipsis](#ellipsis) |
| ellipsis | 自动溢出省略为对象时不能设置省略行数、是否可展开、onExpand 展开事件。不同于 Typography.ParagraphText 组件自身不带 100% 宽度样式,因而默认情况下初次缩略后宽度便不再变化。如果需要自适应宽度,请手工配置宽度样式 | boolean \| [Omit<ellipsis, 'expandable' \| 'rows' \| 'onExpand'>](#ellipsis) | false | [ellipsis](#ellipsis) |
| keyboard | 添加键盘样式 | boolean | false | 4.3.0 |
| mark | 添加标记样式 | boolean | false | |
| onClick | 点击 Text 时的回调 | (event) => void | - | |

View File

@ -83,6 +83,10 @@ toc: false
- https://mastergo-local-default.oss-cn-beijing.aliyuncs.com/ant-design-mastergo.svg
- 可在「MasterGo」在线免费使用的全套组件和模板
- https://mastergo.com/community/?utm_source=antdesign&utm_medium=link&utm_campaign=resource&cata_name=AntDesign
- Raycast 拓展
- https://gw.alipayobjects.com/zos/basement_prod/5edc7f4d-3302-4710-963b-7b6c77ea8d06.svg
- mac 用户可使用 Raycast 快速打开 Ant Design 组件
- https://www.raycast.com/crazyair/antd-open-browser
## 文章

View File

@ -111,6 +111,7 @@
"@ant-design/react-slick": "~0.29.1",
"@babel/runtime": "^7.18.3",
"@ctrl/tinycolor": "^3.4.0",
"@testing-library/user-event": "^14.4.2",
"classnames": "^2.2.6",
"copy-to-clipboard": "^3.2.0",
"dayjs": "^1.11.1",
@ -125,7 +126,7 @@
"rc-field-form": "~1.27.0",
"rc-image": "~5.7.0",
"rc-input": "~0.1.2",
"rc-input-number": "~7.3.5",
"rc-input-number": "~7.3.9",
"rc-mentions": "~1.9.1",
"rc-menu": "~9.6.3",
"rc-motion": "^2.6.1",
@ -136,7 +137,7 @@
"rc-rate": "~2.9.0",
"rc-resize-observer": "^1.2.0",
"rc-segmented": "~2.1.0",
"rc-select": "~14.1.1",
"rc-select": "~14.1.13",
"rc-slider": "~10.0.0",
"rc-steps": "~4.1.0",
"rc-switch": "~4.0.0",
@ -230,7 +231,7 @@
"identity-obj-proxy": "^3.0.0",
"immer": "^9.0.1",
"immutability-helper": "^3.0.0",
"inquirer": "^8.0.0",
"inquirer": "^9.1.2",
"intersection-observer": "^0.12.0",
"isomorphic-fetch": "^3.0.0",
"jest": "^28.0.3",

View File

@ -36,6 +36,7 @@ const MAINTAINERS = [
'fireairforce',
'kerm1it',
'madccc',
'MadCcc',
].map(author => author.toLowerCase());
const cwd = process.cwd();

View File

@ -4,14 +4,7 @@ import { FormattedMessage, injectIntl } from 'react-intl';
import classNames from 'classnames';
import { Row, Col, Affix, Tooltip } from 'antd';
import { getChildren } from 'jsonml.js/lib/utils';
import {
CodeFilled,
CodeOutlined,
BugFilled,
BugOutlined,
ExperimentOutlined,
ExperimentFilled,
} from '@ant-design/icons';
import { CodeFilled, CodeOutlined, BugFilled, BugOutlined } from '@ant-design/icons';
import Demo from './Demo';
import EditButton from './EditButton';
import { ping, getMetaDescription } from '../utils';
@ -25,7 +18,6 @@ class ComponentDoc extends React.Component {
expandAll: false,
visibleAll: process.env.NODE_ENV !== 'production',
showRiddleButton: false,
react17Demo: false,
};
componentDidMount() {
@ -108,7 +100,7 @@ class ComponentDoc extends React.Component {
} = this.props;
const { content, meta } = doc;
const demoValues = Object.keys(demos).map(key => demos[key]);
const { expandAll, visibleAll, showRiddleButton, react17Demo } = this.state;
const { expandAll, visibleAll, showRiddleButton } = this.state;
const isSingleCol = meta.cols === 1;
const leftChildren = [];
const rightChildren = [];
@ -131,7 +123,6 @@ class ComponentDoc extends React.Component {
location={location}
theme={theme}
setIframeTheme={setIframeTheme}
react18={react17Demo}
/>
);
if (index % 2 === 0 || isSingleCol) {
@ -220,27 +211,6 @@ class ComponentDoc extends React.Component {
<BugOutlined className={expandTriggerClass} onClick={this.handleVisibleToggle} />
)}
</Tooltip>
<Tooltip
title={
<FormattedMessage
id={`app.component.examples.${
react17Demo ? 'openDemoWithReact18' : 'openDemoNotReact18'
}`}
/>
}
>
{react17Demo ? (
<ExperimentFilled
className={expandTriggerClass}
onClick={this.handleDemoVersionToggle}
/>
) : (
<ExperimentOutlined
className={expandTriggerClass}
onClick={this.handleDemoVersionToggle}
/>
)}
</Tooltip>
</span>
</h2>
</section>

View File

@ -48,14 +48,13 @@ class Demo extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const { codeExpand, copied, copyTooltipOpen } = this.state;
const { expand, theme, showRiddleButton, react18 } = this.props;
const { expand, theme, showRiddleButton } = this.props;
return (
(codeExpand || expand) !== (nextState.codeExpand || nextProps.expand) ||
copied !== nextState.copied ||
copyTooltipOpen !== nextState.copyTooltipOpen ||
nextProps.theme !== theme ||
nextProps.showRiddleButton !== showRiddleButton ||
nextProps.react18 !== react18
nextProps.showRiddleButton !== showRiddleButton
);
}
@ -137,7 +136,6 @@ class Demo extends React.Component {
intl: { locale },
theme,
showRiddleButton,
react18,
} = props;
const { copied, copyTooltipOpen } = state;
if (!this.liveDemo) {
@ -206,13 +204,13 @@ class Demo extends React.Component {
);
dependencies['@ant-design/icons'] = 'latest';
dependencies.react = react18 ? '^18.0.0' : '^17.0.0';
dependencies['react-dom'] = react18 ? '^18.0.0' : '^17.0.0';
dependencies.react = '^18.0.0';
dependencies['react-dom'] = '^18.0.0';
const codepenPrefillConfig = {
title: `${localizedTitle} - antd@${dependencies.antd}`,
html,
js: `${react18 ? 'const { createRoot } = ReactDOM;\n' : ''}${sourceCode
js: `${'const { createRoot } = ReactDOM;\n'}${sourceCode
.replace(/import\s+(?:React,\s+)?{(\s+[^}]*\s+)}\s+from\s+'react'/, `const { $1 } = React;`)
.replace(/import\s+{(\s+[^}]*\s+)}\s+from\s+'antd';/, 'const { $1 } = antd;')
.replace(/import\s+{(\s+[^}]*\s+)}\s+from\s+'@ant-design\/icons';/, 'const { $1 } = icons;')
@ -224,20 +222,17 @@ class Demo extends React.Component {
'const { $1 } = ReactRouterDOM;',
)
.replace(/([A-Za-z]*)\s+as\s+([A-Za-z]*)/, '$1:$2')
.replace(/export default/, 'const ComponentDemo =')}\n\n${
react18
? 'createRoot(mountNode).render(<ComponentDemo />)'
: 'ReactDOM.render(<ComponentDemo />, mountNode)'
};\n`,
.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: prefillStyle,
editors: '001',
// eslint-disable-next-line no-undef
css_external: `https://unpkg.com/antd@${antdReproduceVersion}/dist/antd.css`,
js_external: [
react18 ? 'react@18/umd/react.development.js' : 'react@16.x/umd/react.development.js',
react18
? 'react-dom@18/umd/react-dom.development.js'
: 'react-dom@16.x/umd/react-dom.development.js',
'react@18/umd/react.development.js',
'react-dom@18/umd/react-dom.development.js',
// eslint-disable-next-line no-undef
`antd@${antdReproduceVersion}/dist/antd-with-locales.js`,
`@ant-design/icons/dist/index.umd.js`,
@ -253,15 +248,10 @@ class Demo extends React.Component {
title: `${localizedTitle} - antd@${dependencies.antd}`,
js: `${
/import React(\D*)from 'react';/.test(sourceCode) ? '' : `import React from 'react';\n`
}${
react18
? `import { createRoot } from 'react-dom/client';\n`
: `import ReactDOM from 'react-dom';\n`
}${sourceCode.replace(/export default/, 'const ComponentDemo =')}\n\n${
react18
? 'createRoot(mountNode).render(<ComponentDemo />)'
: 'ReactDOM.render(<ComponentDemo />, mountNode)'
};\n`,
}import { createRoot } from 'react-dom/client';\n${sourceCode.replace(
/export default/,
'const ComponentDemo =',
)}\n\ncreateRoot(mountNode).render(<ComponentDemo />);\n`,
css: prefillStyle,
json: JSON.stringify(
{
@ -296,20 +286,12 @@ ${parsedSourceCode}
.replace('</style>', '')
.replace('<style>', '');
const indexJsContent = react18
? `
const indexJsContent = `
import React from 'react';
import { createRoot } from 'react-dom/client';
import Demo from './demo';
createRoot(document.getElementById('container')).render(<Demo />);
`
: `
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.getElementById('container'));
`;
const codesandboxPackage = {
@ -317,8 +299,8 @@ ReactDOM.render(<Demo />, document.getElementById('container'));
main: 'index.js',
dependencies: {
...dependencies,
react: react18 ? '^18.0.0' : '^16.14.0',
'react-dom': react18 ? '^18.0.0' : '^16.14.0',
react: '^18.0.0',
'react-dom': '^18.0.0',
'react-scripts': '^4.0.0',
},
devDependencies: {