Merge branch 'master' into next-merge-master

This commit is contained in:
MadCcc 2022-08-08 15:12:31 +08:00
commit dbff63666a
30 changed files with 287 additions and 170 deletions

View File

@ -148,6 +148,7 @@ module.exports = {
'jest/no-done-callback': 0,
'jest/valid-title': 0,
'jest/no-conditional-expect': 0,
'jest/no-standalone-expect': 0,
'unicorn/better-regex': 2,
'unicorn/prefer-string-trim-start-end': 2,

View File

@ -1,9 +1,8 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import Alert from '..';
import accessibilityTest from '../../../tests/shared/accessibilityTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep } from '../../../tests/utils';
import { fireEvent, render, sleep, act } from '../../../tests/utils';
import Button from '../../button';
import Popconfirm from '../../popconfirm';
import Tooltip from '../../tooltip';
@ -33,13 +32,13 @@ describe('Alert', () => {
/>,
);
jest.useFakeTimers();
fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
act(() => {
jest.useFakeTimers();
fireEvent.click(container.querySelector('.ant-alert-close-icon')!);
jest.runAllTimers();
jest.useRealTimers();
});
expect(onClose).toHaveBeenCalled();
jest.useRealTimers();
});
describe('action of Alert', () => {

View File

@ -221,4 +221,11 @@ describe('Avatar Render', () => {
expect(wrapper.querySelector('[crossorigin]')).toBeNull();
expect(getByRole('img').getAttribute('crossOrigin')).toEqual(null);
});
it('clickable', async () => {
const onClick = jest.fn();
const { container } = render(<Avatar onClick={onClick}>TestString</Avatar>);
fireEvent.click(container.querySelector('.ant-avatar-string'));
expect(onClick).toHaveBeenCalled();
});
});

View File

@ -33,6 +33,7 @@ export interface AvatarProps {
children?: React.ReactNode;
alt?: string;
crossOrigin?: '' | 'anonymous' | 'use-credentials';
onClick?: (e?: React.MouseEvent<HTMLElement>) => void;
/* callback when img load error */
/* return false to prevent Avatar show default fallback behavior, then you can do fallback by your self */
onError?: () => boolean;

View File

@ -9,6 +9,7 @@ import zhCN from '../../locale/zh_CN';
import Modal from '../../modal';
import Pagination from '../../pagination';
import TimePicker from '../../time-picker';
import { act } from '../../../tests/utils';
describe('ConfigProvider.Locale', () => {
function $$(className) {
@ -44,7 +45,9 @@ describe('ConfigProvider.Locale', () => {
title: 'title',
content: 'Some descriptions',
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
jest.useRealTimers();
};

View File

@ -3,6 +3,7 @@ import React from 'react';
import ConfigProvider from '..';
import Affix from '../../affix';
import Anchor from '../../anchor';
import { act } from '../../../tests/utils';
describe('ConfigProvider.getTargetContainer', () => {
it('Affix', () => {
@ -16,7 +17,9 @@ describe('ConfigProvider.getTargetContainer', () => {
</ConfigProvider>,
);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(getTargetContainer).toHaveBeenCalled();
jest.useRealTimers();
@ -33,7 +36,9 @@ describe('ConfigProvider.getTargetContainer', () => {
</ConfigProvider>,
);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(getTargetContainer).toHaveBeenCalled();
jest.useRealTimers();

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import Form from '..';
import { fireEvent, render, sleep } from '../../../tests/utils';
import { fireEvent, render, sleep, act } from '../../../tests/utils';
import Button from '../../button';
import Input from '../../input';
@ -43,8 +42,8 @@ describe('Form.List', () => {
);
function operate(className) {
fireEvent.click(container.querySelector(className));
act(() => {
fireEvent.click(container.querySelector(className));
jest.runAllTimers();
});
}

View File

@ -81,6 +81,7 @@ import {
Transfer,
} from '../..';
import mountTest from '../../../tests/shared/mountTest';
import { act } from '../../../tests/utils';
import arEG from '../ar_EG';
import azAZ from '../az_AZ';
import bgBG from '../bg_BG';
@ -296,7 +297,9 @@ describe('Locale Provider', () => {
Modal.confirm({
title: 'Hello World!',
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
jest.useRealTimers();
}

View File

@ -4,6 +4,7 @@ import Mentions from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { act } from '../../../tests/utils';
const { getMentions } = Mentions;
@ -68,7 +69,9 @@ describe('Mentions', () => {
expect(onFocus).toHaveBeenCalled();
wrapper.find('textarea').simulate('blur');
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
wrapper.update();
expect(wrapper.find('.ant-mentions').hasClass('ant-mentions-focused')).toBeFalsy();
expect(onBlur).toHaveBeenCalled();

View File

@ -7,11 +7,10 @@ import {
} from '@ant-design/icons';
import { mount } from 'enzyme';
import React, { useState } from 'react';
import { act } from 'react-dom/test-utils';
import Menu from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import { fireEvent, render, act } from '../../../tests/utils';
import Layout from '../../layout';
import Tooltip from '../../tooltip';
import initCollapseMotion from '../../_util/motion';
@ -378,8 +377,8 @@ describe('Menu', () => {
wrapper.setProps({ inlineCollapsed: true });
act(() => {
jest.runAllTimers();
wrapper.update();
});
wrapper.update();
expect(wrapper.find('ul.ant-menu-root').hasClass('ant-menu-vertical')).toBeTruthy();
expect(wrapper.find('PopupTrigger').prop('visible')).toBeFalsy();
@ -388,8 +387,8 @@ describe('Menu', () => {
wrapper.setProps({ inlineCollapsed: false });
act(() => {
jest.runAllTimers();
wrapper.update();
});
wrapper.update();
expect(wrapper.find('ul.ant-menu-sub').last().hasClass('ant-menu-inline')).toBeTruthy();
expect(wrapper.find('InlineSubMenuList').prop('open')).toBeTruthy();
@ -708,8 +707,8 @@ describe('Menu', () => {
act(() => {
jest.runAllTimers();
wrapper.update();
});
wrapper.update();
expect(wrapper.find('.ant-tooltip-inner').length).toBe(0);
});
@ -752,7 +751,9 @@ describe('Menu', () => {
);
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter');
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
wrapper.update();
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();

View File

@ -4,9 +4,9 @@ import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning';
import * as React from 'react';
import TestUtils, { act } from 'react-dom/test-utils';
import TestUtils from 'react-dom/test-utils';
import Modal from '..';
import { sleep } from '../../../tests/utils';
import { sleep, act } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import destroyFns from '../destroyFns';
@ -78,7 +78,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
content: 'some descriptions',
...args,
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
jest.useRealTimers();
}
@ -87,7 +89,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
confirm({
content: 'some descriptions',
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(document.querySelector('.ant-modal-confirm-title')).toBe(null);
jest.useRealTimers();
});
@ -150,18 +154,26 @@ describe('Modal.confirm triggers callbacks correctly', () => {
onCancel,
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
await sleep();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
keyCode: KeyCode.ESC,
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
await sleep(0);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
expect(onCancel).toHaveBeenCalledTimes(1);
@ -197,18 +209,26 @@ describe('Modal.confirm triggers callbacks correctly', () => {
open();
jest.useFakeTimers();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
await sleep();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect($$('.ant-modal-confirm')).toHaveLength(1);
await sleep();
$$('.ant-btn')[0].click();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
await sleep();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect($$('.ant-modal-confirm')).toHaveLength(0);
jest.useRealTimers();
@ -358,7 +378,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('new title');
expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('new content');
instance.destroy();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
jest.useRealTimers();
});
});
@ -403,7 +425,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
);
expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
instance.destroy();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
jest.useRealTimers();
});
});
@ -487,7 +511,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useFakeTimers();
Modal.destroyAll(); // clear destroyFns
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
const instances = [];
['info', 'success', 'warning', 'error'].forEach(type => {
@ -716,18 +742,26 @@ describe('Modal.confirm triggers callbacks correctly', () => {
onCancel: close => mock(close),
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
await sleep();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
keyCode: KeyCode.ESC,
});
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
await sleep(0);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
expect(mock).toBeCalledWith(expect.any(Function));

View File

@ -4,7 +4,7 @@ import React from 'react';
import Popconfirm from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep } from '../../../tests/utils';
import { fireEvent, render, sleep, act } from '../../../tests/utils';
import Button from '../../button';
describe('Popconfirm', () => {
@ -102,7 +102,9 @@ describe('Popconfirm', () => {
expect(ref.current.getPopupDomNode().className).not.toContain('ant-popover-hidden');
popconfirm.setProps({ visible: false });
popconfirm.update(); // https://github.com/enzymejs/enzyme/issues/2305
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(popconfirm.find('Trigger').props().popupVisible).toBe(false);
jest.useRealTimers();
});

View File

@ -1,10 +1,9 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import Select from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import { fireEvent, render, act } from '../../../tests/utils';
import Icon from '../../icon';
const { Option } = Select;
@ -15,8 +14,8 @@ describe('Select', () => {
rtlTest(Select);
function toggleOpen(container) {
fireEvent.mouseDown(container.querySelector('.ant-select-selector'));
act(() => {
fireEvent.mouseDown(container.querySelector('.ant-select-selector'));
jest.runAllTimers();
});
}
@ -123,7 +122,9 @@ describe('Select', () => {
<Option value="1">1</Option>
</Select>,
);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(asFragment().firstChild).toMatchSnapshot();
});
});

View File

@ -1,8 +1,7 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { act } from 'react-dom/test-utils';
import Table from '..';
import { fireEvent, render, waitFor } from '../../../tests/utils';
import { fireEvent, render, waitFor, act } from '../../../tests/utils';
import Button from '../../button';
import ConfigProvider from '../../config-provider';
import Input from '../../input';

View File

@ -2,9 +2,8 @@
jest.mock('../../_util/scrollTo');
import React from 'react';
import { act } from 'react-dom/test-utils';
import Table from '..';
import { fireEvent, render } from '../../../tests/utils';
import { fireEvent, render, act } from '../../../tests/utils';
import scrollTo from '../../_util/scrollTo';
import { resetWarned } from '../../_util/warning';

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import Table from '..';
import { fireEvent, render } from '../../../tests/utils';
import { fireEvent, render, act } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import { resetWarned } from '../../_util/warning';

View File

@ -3,6 +3,7 @@ import React from 'react';
import Tag from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { act } from '../../../tests/utils';
describe('Tag', () => {
mountTest(Tag);
@ -25,7 +26,9 @@ describe('Tag', () => {
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
wrapper.find('.anticon-close').simulate('click');
expect(onClose).toHaveBeenCalled();
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
wrapper.update();
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(0);
});
@ -38,7 +41,9 @@ describe('Tag', () => {
expect(wrapper.find('.anticon-close').length).toBe(1);
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
wrapper.find('.anticon-close').simulate('click');
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
});
@ -81,10 +86,14 @@ describe('Tag', () => {
const wrapper = mount(<Tag visible />);
expect(wrapper.render()).toMatchSnapshot();
wrapper.setProps({ visible: false });
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(wrapper.render()).toMatchSnapshot();
wrapper.setProps({ visible: true });
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(wrapper.render()).toMatchSnapshot();
});
@ -92,10 +101,14 @@ describe('Tag', () => {
const wrapper = mount(<Tag visible={false} />);
expect(wrapper.render()).toMatchSnapshot();
wrapper.setProps({ visible: true });
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(wrapper.render()).toMatchSnapshot();
wrapper.setProps({ visible: false });
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(wrapper.render()).toMatchSnapshot();
});
});

View File

@ -1,8 +1,7 @@
import { LikeOutlined, SmileOutlined } from '@ant-design/icons';
import { act } from '@testing-library/react';
import * as copyObj from 'copy-to-clipboard';
import React from 'react';
import { fireEvent, render, waitFor } from '../../../tests/utils';
import { fireEvent, render, waitFor, act } from '../../../tests/utils';
import Base from '../Base';
@ -90,7 +89,9 @@ describe('Typography copy', () => {
jest.useFakeTimers();
fireEvent.click(wrapper.querySelectorAll('.ant-typography-copy')[0]);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
unmount();
jest.useRealTimers();

View File

@ -5,7 +5,7 @@ import { resetWarned } from 'rc-util/lib/warning';
import React from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep, waitFor } from '../../../tests/utils';
import { fireEvent, render, sleep, waitFor, act } from '../../../tests/utils';
import Base from '../Base';
import Link from '../Link';
import Paragraph from '../Paragraph';
@ -104,7 +104,9 @@ describe('Typography', () => {
}
fireEvent.mouseEnter(wrapper.querySelector('.ant-typography-copy'));
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
if (tooltips === undefined || tooltips === true) {
await waitFor(() => {
@ -271,7 +273,9 @@ describe('Typography', () => {
expect(onStart).not.toHaveBeenCalled();
}
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-edit')[0]);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
if (tooltip === undefined || tooltip === true) {
await waitFor(() => {

View File

@ -1,9 +1,8 @@
/* eslint-disable react/no-string-refs, react/prefer-es6-class */
import React from 'react';
import { act } from 'react-dom/test-utils';
import Upload from '..';
import mountTest from '../../../tests/shared/mountTest';
import { fireEvent, render, waitFor } from '../../../tests/utils';
import { fireEvent, render, waitFor, act } from '../../../tests/utils';
import { setup, teardown } from './mock';
describe('Upload.Dragger', () => {
@ -26,7 +25,7 @@ describe('Upload.Dragger', () => {
},
});
await act(() => {
act(() => {
jest.runAllTimers();
});

View File

@ -2,11 +2,10 @@
import produce from 'immer';
import { cloneDeep } from 'lodash';
import React from 'react';
import { act } from 'react-dom/test-utils';
import Upload from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep } from '../../../tests/utils';
import { fireEvent, render, sleep, act } from '../../../tests/utils';
import Form from '../../form';
import { resetWarned } from '../../_util/warning';
import { getFileItem, isImageUrl, removeFileItem } from '../utils';
@ -322,7 +321,9 @@ describe('Upload', () => {
const { rerender } = render(<Upload ref={ref} />);
expect(ref.current.fileList).toEqual([]);
rerender(<Upload ref={ref} fileList={fileList} />);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
expect(ref.current.fileList).toEqual(fileList);
jest.useRealTimers();
});
@ -725,7 +726,7 @@ describe('Upload', () => {
await Promise.resolve();
}
});
await act(() => {
act(() => {
jest.runAllTimers();
});
await act(async () => {
@ -945,7 +946,7 @@ describe('Upload', () => {
});
// Motion leave status change: start > active
await act(() => {
act(() => {
jest.runAllTimers();
});

View File

@ -1,7 +1,6 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import Upload from '..';
import { fireEvent, render, sleep, waitFor } from '../../../tests/utils';
import { fireEvent, render, sleep, waitFor, act } from '../../../tests/utils';
import Form from '../../form';
import UploadList from '../UploadList';
import { previewImage } from '../utils';
@ -263,7 +262,7 @@ describe('Upload List', () => {
// Error message
fireEvent.mouseEnter(wrapper.querySelector('.ant-upload-list-item'));
await act(() => {
act(() => {
jest.runAllTimers();
});
@ -574,7 +573,9 @@ describe('Upload List', () => {
/>,
);
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
unmount();
@ -1132,13 +1133,15 @@ describe('Upload List', () => {
await waitPromise();
// Wait for mock request finish request
jest.runAllTimers();
act(() => {
jest.runAllTimers();
});
// Basic called times
expect(onChange).toHaveBeenCalled();
// Check for images
await act(() => {
act(() => {
jest.runAllTimers();
});
const afterImgNode = wrapper.container.querySelectorAll(
@ -1313,7 +1316,7 @@ describe('Upload List', () => {
expect(uploadRef.current.fileList).toHaveLength(fileNames.length);
await act(() => {
act(() => {
jest.runAllTimers();
});
expect(uploadRef.current.fileList).toHaveLength(fileNames.length);

9
site/theme/en-US.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
interface ENLocale {
locale: 'en-US';
messages: {
[key: PropertyKey]: string;
};
}
const enLocale: ENLocale;
export default enLocale;

View File

@ -1,7 +1,6 @@
/* eslint-disable camelcase */
import React from 'react';
import AntdIcon, { createFromIconfontCN } from '@ant-design/icons';
import { withThemeSuffix, removeTypeTheme, getThemeFromTypeName } from './utils';
import warning from '../../../../components/_util/warning';
@ -9,7 +8,16 @@ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_1329669_t1u72b9zk8s.js',
});
const OldIcon = props => {
interface IconProps {
type: string;
theme: string;
}
interface CreateIconfont {
createFromIconfontCN: typeof createFromIconfontCN;
}
const OldIcon: React.FC<IconProps> = props => {
const { type, theme } = props;
let computedType = type;
if (theme) {
@ -25,12 +33,8 @@ const OldIcon = props => {
return <IconFont {...props} type={`icon-${computedType}`} />;
};
const Icon = props => {
if (typeof props.type === 'string') {
return <OldIcon {...props} />;
}
return <AntdIcon {...props} />;
};
const Icon: React.FC<IconProps> & CreateIconfont = props =>
typeof props.type === 'string' ? <OldIcon {...props} /> : <AntdIcon {...props} />;
Icon.createFromIconfontCN = createFromIconfontCN;

View File

@ -14,8 +14,8 @@ const fillTester = /-fill$/;
const outlineTester = /-o$/;
const twoToneTester = /-twotone$/;
export function getThemeFromTypeName(type) {
let result = null;
export function getThemeFromTypeName(type: string) {
let result: string | null = null;
if (fillTester.test(type)) {
result = 'filled';
} else if (outlineTester.test(type)) {
@ -26,12 +26,12 @@ export function getThemeFromTypeName(type) {
return result;
}
export function removeTypeTheme(type) {
export function removeTypeTheme(type: string): string {
return type.replace(fillTester, '').replace(outlineTester, '').replace(twoToneTester, '');
}
export function withThemeSuffix(type, theme) {
let result = type;
export function withThemeSuffix(type: string, theme: string): string {
let result: string = type;
if (theme === 'filled') {
result += '-fill';
} else if (theme === 'outlined') {

View File

@ -31,7 +31,7 @@ export interface HeaderProps {
intl: { locale: string };
location: { pathname: string; query: any };
router: any;
themeConfig: { docVersions: Record<string, string> };
themeConfig?: { docVersions: Record<string, string> };
changeDirection: (direction: DirectionType) => void;
}
@ -202,7 +202,7 @@ const Header: React.FC<HeaderProps & WrappedComponentProps<'intl'>> = props => {
const { menuVisible, windowWidth, searching, showTechUIButton } = headerState;
const docVersions: Record<string, string> = {
[antdVersion]: antdVersion,
...themeConfig.docVersions,
...themeConfig?.docVersions,
};
const versionOptions = Object.keys(docVersions).map(version => (
<Option value={docVersions[version]} key={version}>

View File

@ -4,6 +4,9 @@ import type { DirectionType } from 'antd/es/config-provider';
export interface SiteContextProps {
isMobile: boolean;
direction: DirectionType;
theme?: string;
setTheme?: (theme: string, persist?: boolean) => void;
setIframeTheme?: (iframeNode: HTMLIFrameElement, theme: string) => void;
}
const SiteContext = React.createContext<SiteContextProps>({

View File

@ -1,24 +1,28 @@
import { presetDarkPalettes, presetPalettes } from '@ant-design/colors';
import { createCache, StyleProvider } from '@ant-design/cssinjs';
import { setTwoToneColor } from '@ant-design/icons';
import { ConfigProvider, theme as antdTheme } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN';
import { browserHistory } from 'bisheng/router';
import classNames from 'classnames';
import 'dayjs/locale/zh-cn';
/* eslint-disable class-methods-use-this */
import React from 'react';
import ReactDOM from 'react-dom';
import { Helmet, HelmetProvider } from 'react-helmet-async';
import { IntlProvider } from 'react-intl';
import themeSwitcher from 'theme-switcher';
import type { TwoToneColor } from '@ant-design/icons';
import { setTwoToneColor } from '@ant-design/icons';
import { ConfigProvider, theme as antdTheme } from 'antd';
import { browserHistory } from 'bisheng/router';
import { createCache, StyleProvider } from '@ant-design/cssinjs';
import type { SeedToken } from 'antd/es/theme';
import classNames from 'classnames';
import { presetDarkPalettes, presetPalettes } from '@ant-design/colors';
import zhCN from 'antd/lib/locale/zh_CN';
import type { DirectionType } from 'antd/es/config-provider';
import enLocale from '../../en-US';
import cnLocale from '../../zh-CN';
import * as utils from '../utils';
import Header from './Header';
import type { SiteContextProps } from './SiteContext';
import SiteContext from './SiteContext';
import defaultSeedToken from '../../../../components/theme/themes/seed';
import DynamicTheme from './DynamicTheme';
import 'moment/locale/zh-cn';
if (typeof window !== 'undefined' && navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations().then(registrations => {
@ -36,12 +40,12 @@ if (typeof window !== 'undefined') {
require('../../static/style');
// Expose to iframe
window.react = React;
window['react-dom'] = ReactDOM;
(window as any).react = React;
(window as any)['react-dom'] = ReactDOM;
// eslint-disable-next-line global-require
window.antd = require('antd');
(window as any).antd = require('antd');
// eslint-disable-next-line global-require
window['@ant-design/icons'] = require('@ant-design/icons');
(window as any)['@ant-design/icons'] = require('@ant-design/icons');
// Error log statistic
window.addEventListener('error', e => {
@ -56,7 +60,7 @@ if (typeof window !== 'undefined') {
const RESPONSIVE_MOBILE = 768;
// for dark.css timestamp to remove cache
const timestamp = new Date().getTime();
const timestamp = Date.now();
const themeMap = {
dark: `/dark.css?${timestamp}`,
compact: `/compact.css?${timestamp}`,
@ -69,17 +73,38 @@ const { switcher } = themeSwitcher(themeConfig);
// Pass to global since bisheng do not have the process for wrapper
const styleCache = createCache();
if (typeof global !== 'undefined') {
global.styleCache = styleCache;
(global as any).styleCache = styleCache;
}
export default class Layout extends React.Component {
interface LayoutPropsType {
location: any;
router: any;
helmetContext: any;
children: React.ReactNode;
}
interface LayoutStateType {
appLocale: typeof cnLocale | typeof enLocale;
theme: string;
isMobile: boolean;
direction: DirectionType;
setTheme: SiteContextProps['setTheme'];
setIframeTheme: SiteContextProps['setIframeTheme'];
v5theme: string;
designToken: SeedToken;
hashedStyle: boolean;
}
export default class Layout extends React.Component<LayoutPropsType, LayoutStateType> {
static contextType = SiteContext;
timer: NodeJS.Timeout | null = null;
isBeforeComponent = false;
syncIframeThemeId = null;
syncIframeThemeId?: number;
constructor(props) {
constructor(props: LayoutPropsType) {
super(props);
const { pathname } = props.location;
const appLocale = utils.isZhCN(pathname) ? cnLocale : enLocale;
@ -87,8 +112,9 @@ export default class Layout extends React.Component {
this.state = {
appLocale,
theme: 'default',
setTheme: this.setTheme,
direction: 'ltr',
isMobile: false,
setTheme: this.setTheme,
setIframeTheme: this.setIframeTheme,
v5theme: 'default',
designToken: defaultSeedToken,
@ -98,50 +124,40 @@ export default class Layout extends React.Component {
componentDidMount() {
const { location, router } = this.props;
router.listen(({ pathname, search }) => {
router.listen(({ pathname, search }: any) => {
const { theme } = this.props.location.query;
if (typeof window.ga !== 'undefined') {
window.ga('send', 'pageview', pathname + search);
if (typeof (window as any).ga !== 'undefined') {
(window as any).ga('send', 'pageview', pathname + search);
}
// eslint-disable-next-line
if (typeof window._hmt !== 'undefined') {
// eslint-disable-next-line
window._hmt.push(['_trackPageview', pathname + search]);
if (typeof (window as any)._hmt !== 'undefined') {
(window as any)._hmt.push(['_trackPageview', pathname + search]);
}
const componentPage = /^\/?components/.test(pathname);
// only component page can use `dark` theme
if (!componentPage) {
this.isBeforeComponent = false;
this.setTheme('default', false);
this.setTheme?.('default', false);
} else if (theme && !this.isBeforeComponent) {
this.isBeforeComponent = true;
this.setTheme(theme, false);
this.setTheme?.(theme, false);
}
});
if (location.query.theme && /^\/?components/.test(location.pathname)) {
this.isBeforeComponent = true;
this.setTheme(location.query.theme, false);
this.setTheme?.(location.query.theme, false);
} else {
this.isBeforeComponent = false;
this.setTheme('default', false);
this.setTheme?.('default', false);
}
if (location.query.direction) {
this.setState({
direction: location.query.direction,
});
} else {
this.setState({
direction: 'ltr',
});
}
this.setState({ direction: location.query.direction || 'ltr' });
const nprogressHiddenStyle = document.getElementById('nprogress-style');
if (nprogressHiddenStyle) {
this.timer = setTimeout(() => {
nprogressHiddenStyle.parentNode.removeChild(nprogressHiddenStyle);
nprogressHiddenStyle.parentNode?.removeChild(nprogressHiddenStyle);
}, 0);
}
@ -149,7 +165,7 @@ export default class Layout extends React.Component {
window.addEventListener('resize', this.updateMobileMode);
// Sync iframe theme with current theme
this.syncIframeThemeId = setInterval(() => {
this.syncIframeThemeId = window.setInterval(() => {
const { designToken, hashedStyle } = this.state;
const content = JSON.stringify({
action: 'sync.theme',
@ -157,16 +173,19 @@ export default class Layout extends React.Component {
hashed: hashedStyle,
});
document.querySelectorAll('iframe.iframe-demo').forEach(iframe => {
iframe.contentWindow.postMessage(content);
document.querySelectorAll<HTMLIFrameElement>('iframe.iframe-demo').forEach(iframe => {
iframe.contentWindow?.postMessage(content);
});
}, 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
clearInterval(this.syncIframeThemeId);
clearTimeout(this.timer as unknown as number);
clearInterval(this.syncIframeThemeId as unknown as number);
window.removeEventListener('resize', this.updateMobileMode);
if (this.timer) {
clearTimeout(this.timer);
}
}
updateMobileMode = () => {
@ -179,19 +198,16 @@ export default class Layout extends React.Component {
}
};
setIframeTheme = (iframeNode, theme) => {
iframeNode.contentWindow.postMessage(
setIframeTheme: LayoutStateType['setIframeTheme'] = (iframeNode, theme) => {
iframeNode.contentWindow?.postMessage(
JSON.stringify({
action: 'change.theme',
data: {
themeConfig,
theme,
},
data: { themeConfig, theme },
}),
);
};
setTheme = (theme, persist = true) => {
setTheme: LayoutStateType['setTheme'] = (theme, persist = true) => {
if (typeof window === 'undefined') {
return;
}
@ -203,24 +219,23 @@ export default class Layout extends React.Component {
const iframeNodes = document.querySelectorAll('.iframe-demo');
// loop element node
[].forEach.call(iframeNodes, iframeNode => {
this.setIframeTheme(iframeNode, theme);
[].forEach.call(iframeNodes, (iframeNode: HTMLIFrameElement) => {
this.setIframeTheme?.(iframeNode, theme);
});
this.setState({
theme,
});
this.setState({ theme });
const iconTwoToneThemeMap = {
dark: [presetDarkPalettes.blue.primary, '#111d2c'],
default: presetPalettes.blue.primary,
};
setTwoToneColor(iconTwoToneThemeMap[theme] || iconTwoToneThemeMap.default);
} as const;
setTwoToneColor(
(iconTwoToneThemeMap[theme as keyof typeof iconTwoToneThemeMap] ||
iconTwoToneThemeMap.default) as TwoToneColor,
);
};
changeDirection = direction => {
this.setState({
direction,
});
changeDirection = (direction: DirectionType): void => {
this.setState({ direction });
const { pathname, hash, query } = this.props.location;
if (direction === 'ltr') {
delete query.direction;
@ -257,15 +272,14 @@ export default class Layout extends React.Component {
: 'An enterprise-class UI design language and React UI library with a set of high-quality React components, one of best React UI library for enterprises';
return (
<StyleProvider cache={styleCache}>
{/* eslint-disable-next-line react/jsx-no-constructed-context-values */}
<SiteContext.Provider value={{ isMobile, direction, theme, setTheme, setIframeTheme }}>
<HelmetProvider context={helmetContext}>
<Helmet encodeSpecialCharacters={false}>
<html
lang={appLocale.locale === 'zh-CN' ? 'zh' : 'en'}
data-direction={direction}
className={classNames({
[`rtl`]: direction === 'rtl',
})}
className={classNames({ [`rtl`]: direction === 'rtl' })}
/>
<title>{title}</title>
<link
@ -286,7 +300,7 @@ export default class Layout extends React.Component {
defaultLocale="en-US"
>
<ConfigProvider
locale={appLocale.locale === 'zh-CN' ? zhCN : null}
locale={appLocale.locale === 'zh-CN' ? zhCN : undefined}
direction={direction}
theme={{
token: designToken,
@ -299,17 +313,19 @@ export default class Layout extends React.Component {
{children}
<DynamicTheme
componentName={this.props.params?.children?.replace('-cn', '')}
defaultToken={{
theme: v5theme,
...designToken,
hashed: hashedStyle,
}}
componentName={(this.props as any).params?.children?.replace('-cn', '')}
defaultToken={
{
theme: v5theme,
...designToken,
hashed: hashedStyle,
} as any
}
onChangeTheme={newToken => {
console.log('Change Theme:', newToken);
const { hashed, theme, ...restToken } = newToken;
const { hashed, newTheme, ...restToken } = newToken as any;
this.setState({
v5theme: theme,
v5theme: newTheme,
designToken: restToken,
hashedStyle: hashed,
});

9
site/theme/zh-CN.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
interface CNLocale {
locale: 'zh-CN';
messages: {
[key: PropertyKey]: string;
};
}
const cnLocale: CNLocale;
export default cnLocale;

View File

@ -1,9 +1,8 @@
import type { RenderOptions } from '@testing-library/react';
import { render } from '@testing-library/react';
import MockDate from 'mockdate';
import type { ReactElement } from 'react';
import React, { StrictMode } from 'react';
import { act } from 'react-dom/test-utils';
import type { RenderOptions } from '@testing-library/react';
import { render, act } from '@testing-library/react';
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';