diff --git a/.antd-tools.config.js b/.antd-tools.config.js index 73502a35fc..ee9de399e0 100644 --- a/.antd-tools.config.js +++ b/.antd-tools.config.js @@ -6,6 +6,14 @@ const defaultVars = require('./scripts/default-vars'); const darkVars = require('./scripts/dark-vars'); const compactVars = require('./scripts/compact-vars'); +function generateThemeFileContent(theme) { + return `const { ${theme}ThemeSingle } = require('./theme');\nconst defaultTheme = require('./default-theme');\n +module.exports = { + ...defaultTheme, + ...${theme}ThemeSingle +}`; +} + // We need compile additional content for antd user function finalizeCompile() { if (fs.existsSync(path.join(__dirname, './lib'))) { @@ -61,11 +69,27 @@ function buildThemeFile(theme, vars) { // eslint-disable-next-line no-console console.log(`Built a entry less file to dist/antd.${theme}.less`); + if (theme === 'default') { + fs.writeFileSync( + path.join(process.cwd(), 'dist', `default-theme.js`), + `module.exports = ${JSON.stringify(vars, null, 2)};\n`, + ); + return; + } + // Build ${theme}.js: dist/${theme}-theme.js, for less-loader + fs.writeFileSync( + path.join(process.cwd(), 'dist', `theme.js`), + `const ${theme}ThemeSingle = ${JSON.stringify(vars, null, 2)};\n`, + { + flag: 'a', + }, + ); + fs.writeFileSync( path.join(process.cwd(), 'dist', `${theme}-theme.js`), - `module.exports = ${JSON.stringify(vars, null, 2)};`, + generateThemeFileContent(theme), ); // eslint-disable-next-line no-console @@ -80,10 +104,47 @@ function finalizeDist() { '@import "../lib/style/index.less";\n@import "../lib/style/components.less";', ); // eslint-disable-next-line no-console + fs.writeFileSync( + path.join(process.cwd(), 'dist', 'theme.js'), + `const defaultTheme = require('./default-theme.js');\n`, + ); + // eslint-disable-next-line no-console console.log('Built a entry less file to dist/antd.less'); buildThemeFile('default', defaultVars); buildThemeFile('dark', darkVars); buildThemeFile('compact', compactVars); + fs.writeFileSync( + path.join(process.cwd(), 'dist', `theme.js`), + ` +function getThemeVariables(options = {}) { + let themeVar = { + 'hack': \`true;@import "\${require.resolve('antd/lib/style/color/colorPalette.less')}";\`, + ...defaultTheme + }; + if(options.dark) { + themeVar = { + ...themeVar, + ...darkThemeSingle + } + } + if(options.compact){ + themeVar = { + ...themeVar, + ...compactThemeSingle + } + } + return themeVar; +} + +module.exports = { + darkThemeSingle, + compactThemeSingle, + getThemeVariables +}`, + { + flag: 'a', + }, + ); } } @@ -94,4 +155,6 @@ module.exports = { dist: { finalize: finalizeDist, }, + generateThemeFileContent, }; +finalizeDist(); diff --git a/.jest.js b/.jest.js index 778b157c91..d162b283be 100644 --- a/.jest.js +++ b/.jest.js @@ -1,5 +1,3 @@ -const libDir = process.env.LIB_DIR; - const transformIgnorePatterns = [ '/dist/', // Ignore modules without es dir. @@ -7,6 +5,13 @@ const transformIgnorePatterns = [ 'node_modules/(?!.*@babel)[^/]+?/(?!(es|node_modules)/)', ]; +function getTestRegex(libDir) { + if (libDir === 'dist') { + return 'demo\\.test\\.js$'; + } + return '.*\\.test\\.(j|t)sx?$'; +} + module.exports = { verbose: true, setupFiles: ['./tests/setup.js'], @@ -28,7 +33,7 @@ module.exports = { '\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor', '\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor', }, - testRegex: `${libDir === 'dist' ? 'demo' : '.*'}\\.test\\.js$`, + testRegex: getTestRegex(process.env.LIB_DIR), collectCoverageFrom: [ 'components/**/*.{ts,tsx}', '!components/*/style/index.tsx', diff --git a/.jest.node.js b/.jest.node.js index d91949fea1..1b76c15762 100644 --- a/.jest.node.js +++ b/.jest.node.js @@ -12,7 +12,7 @@ module.exports = { '\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor', '\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor', }, - testRegex: 'demo\\.test\\.js$', + testRegex: 'demo\\.test\\.(j|t)s$', testEnvironment: 'node', transformIgnorePatterns, snapshotSerializers: ['enzyme-to-json/serializer'], diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index eb1e5711e8..4812758097 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -15,8 +15,58 @@ timeline: true --- +## 4.1.4 + +`2020-04-18` + +- 🐞 Fix dark theme and compact theme not working. [#23243](https://github.com/ant-design/ant-design/pull/23243) +- 🐞 Fix Modal.info executed only once when has argument. [#23360](https://github.com/ant-design/ant-design/pull/23360) +- 🐞 Fix Dropdown submenu background missing. [#23296](https://github.com/ant-design/ant-design/pull/23296) +- 💄 Optimize PageHeader responsive behavior. [#23277](https://github.com/ant-design/ant-design/pull/23277) +- 🐞 Fix TreeSelect render blank in compact mode. [#23231](https://github.com/ant-design/ant-design/pull/23231) +- 🛎 Fix Checkbox and Switch console warning typo (validate -> a valid). [#23240](https://github.com/ant-design/ant-design/pull/23240) [@evancharlton](https://github.com/evancharlton) +- 🐞 Fix Table `rowSelection` params issue when `childrenColumnName` configured. [#23205](https://github.com/ant-design/ant-design/pull/23205) +- Input + - 🐞 Fix Input `type="color"` height issue. [#23351](https://github.com/ant-design/ant-design/pull/23351) + - 🐞 Fix Input width shaking when trigger clear icon. [#23259](https://github.com/ant-design/ant-design/pull/23259) + - 🐞 Fix Input.Search `size` not affected by ConfigProvider `componentSize`. [#23331](https://github.com/ant-design/ant-design/pull/23331) +- Select + - 🐞 Fix multiple Select show remove icon when `disabled`. [#23295](https://github.com/ant-design/ant-design/pull/23295) + - 🐞 Fix Select custom `suffixIcon` cannot be access. [#23274](https://github.com/ant-design/ant-design/pull/23274) + - 🐞 Fix Select search input caret missing in Collapse. [#23250](https://github.com/ant-design/ant-design/pull/23250) +- Globalization + - 🇨🇳 Form validation messages support internalization and add zh_CN locale. [#23165](https://github.com/ant-design/ant-design/pull/23165) [@hengkx](https://github.com/hengkx) + - 🌐 Add missing translations in he_IL. [#23302](https://github.com/ant-design/ant-design/pull/23302) [@MishaKav](https://github.com/MishaKav) + - 🌐 Add missing translations in ru_RU. [#23303](https://github.com/ant-design/ant-design/pull/23303) [@MishaKav](https://github.com/MishaKav) +- TypeScript + - 🔷 Form.Item type upgrade. [#22962](https://github.com/ant-design/ant-design/pull/22962) [@fa93hws](https://github.com/fa93hws) + - 🔷 Tree type upgrade. [#23348](https://github.com/ant-design/ant-design/pull/23348) [@yoyo837](https://github.com/yoyo837) + - 🐞 Pass `popupClassName` prop to `rc-picker`. [#23214](https://github.com/ant-design/ant-design/pull/23214) [@tanmoyopenroot](https://github.com/tanmoyopenroot) +- RTL + - 💄 Fix Select RTL style. [#23235](https://github.com/ant-design/ant-design/pull/23235) + - 💄 Fix Menu RTL style. [#23319](https://github.com/ant-design/ant-design/pull/23319) + +## 4.1.3 + +`2020-04-13` + +- 💄 Adjust Form.Item `label` height style in vertical layout. [#23192](https://github.com/ant-design/ant-design/pull/23192) +- 🐞 Fix `Variable is undefined` when importing dark or compact theme and provide a `getThemeVariables` methold for getting theme variables easily. [#23171](https://github.com/ant-design/ant-design/pull/23171) +- 🐞 Fix PageHeader style breaks when `title` is too long and improve it's responsive design. [#23133](https://github.com/ant-design/ant-design/pull/23133) +- Tabs + - 🐞 Fix Tabs `@tabs-card-height` less variable not working. [#23168](https://github.com/ant-design/ant-design/pull/23168) + - 🐞 Fix Tabs cannot be displayed in Safari 13. [#23151](https://github.com/ant-design/ant-design/pull/23151) [@imhxc](https://github.com/imhxc) +- Table + - 🐞 Fix Table fixed columns cannot pin in Safari 12. [#23161](https://github.com/ant-design/ant-design/pull/23161) + - 🐞 Fix Table `summary` padding in small size. [#23140](https://github.com/ant-design/ant-design/pull/23140) [@someyoungideas](https://github.com/someyoungideas) +- 🐞 Fix Select align style with different size. [#23160](https://github.com/ant-design/ant-design/pull/23160) +- 🐞 Fix RangePicker under Input.Group style issue. [#23149](https://github.com/ant-design/ant-design/pull/23149) +- 🐞 Fix Pagination missing TypeScript definition of `showTitle`. [#23144](https://github.com/ant-design/ant-design/pull/23144) [@DongchengWang](https://github.com/DongchengWang) + ## 4.1.2 +`2020-04-10` + - Menu - 🐞 Fix Menu SubMenu background in dark mode. [#22981](https://github.com/ant-design/ant-design/pull/22981) [@AshoneA](https://github.com/AshoneA) - 🐞 Fix long SubMenu title being overlayed by arrow icon. [#23028](https://github.com/ant-design/ant-design/pull/23028) [@wwyx778](https://github.com/wwyx778) @@ -52,6 +102,8 @@ timeline: true ## 4.1.1 +`2020-04-05` + - 🐞 Fix Tabs panel focus outline style. [#22752](https://github.com/ant-design/ant-design/pull/22752) [@MrHeer](https://github.com/MrHeer) - 🐞 Fix Input affix with popup element can not get click focus. [#22887](https://github.com/ant-design/ant-design/pull/22887) - Table diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index 456516406d..7585609a06 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -15,8 +15,58 @@ timeline: true --- +## 4.1.4 + +`2020-04-18` + +- 🐞 修复暗黑主题和紧凑主题不生效的问题。[#23243](https://github.com/ant-design/ant-design/pull/23243) +- 🐞 修复 Modal.info 等方法的 `onOk` 函数有参数时只触发一次的问题。[#23360](https://github.com/ant-design/ant-design/pull/23360) +- 🐞 修复 Dropdown 弹出菜单背景样式问题。[#23296](https://github.com/ant-design/ant-design/pull/23296) +- 💄 优化 PageHeader 的响应式表现。[#23277](https://github.com/ant-design/ant-design/pull/23277) +- 🐞 修复紧凑模式下树选择出现空白。[#23231](https://github.com/ant-design/ant-design/pull/23231) +- 🛎 修改 Checkbox 和 Switch 中控制台输出的错别字 (validate -> a valid)。[#23240](https://github.com/ant-design/ant-design/pull/23240) [@evancharlton](https://github.com/evancharlton) +- 🐞 修复 Table `rowSelection` 在设置 `childrenColumnName` 时事件参数不正确的问题。[#23205](https://github.com/ant-design/ant-design/pull/23205) +- Input + - 🐞 修复 Input `type="color"` 的高度问题。[#23351](https://github.com/ant-design/ant-design/pull/23351) + - 🐞 修复 Input 设置 `allowClear` 内联展示时,触发清除按钮样式抖动的问题。[#23259](https://github.com/ant-design/ant-design/pull/23259) + - 🐞 修复 Input.Search 全局设置 `size` 不生效问题。[#23331](https://github.com/ant-design/ant-design/pull/23331) +- Select + - 🐞 修复 Select 多选时设置 `disabled` 选项仍然会展示移除按钮的问题。[#23295](https://github.com/ant-design/ant-design/pull/23295) + - 🐞 修复 Select 自定义 `suffixIcon` 无法交互的问题。[#23274](https://github.com/ant-design/ant-design/pull/23274) + - 🐞 修复 Select 输入光标在 Collapse 内不显示的问题。[#23250](https://github.com/ant-design/ant-design/pull/23250) +- 国际化 + - 🌐 Form 校验信息支持国际化并增加中文文案。[#23165](https://github.com/ant-design/ant-design/pull/23165) [@hengkx](https://github.com/hengkx) + - 🌐 完善希伯来语(以色列) 国际化。[#23302](https://github.com/ant-design/ant-design/pull/23302) [@MishaKav](https://github.com/MishaKav) + - 🌐 完善俄语国际化。[#23303](https://github.com/ant-design/ant-design/pull/23303) [@MishaKav](https://github.com/MishaKav) +- TypeScript + - 🔷 更新 Tree 的类型定义。[#23348](https://github.com/ant-design/ant-design/pull/23348) [@yoyo837](https://github.com/yoyo837) + - 🔷 更新 Form Item 的类型定义。[#22962](https://github.com/ant-design/ant-design/pull/22962) [@fa93hws](https://github.com/fa93hws) + - 🐞 修复 Slider 组件 `value` 和 `defaultValue` 文档与 TypeScript 定义不一致的问题。[#23252](https://github.com/ant-design/ant-design/pull/23252) [@DongchengWang](https://github.com/DongchengWang) +- RTL + - 🐞 修复 Menu RTL 样式。[#23319](https://github.com/ant-design/ant-design/pull/23319) + - 💄 修复 Select 的 RTL 样式。[#23235](https://github.com/ant-design/ant-design/pull/23235) + +## 4.1.3 + +`2020-04-13` + +- 💄 调整 Form.Item `label` 在垂直布局下的高度样式。[#23192](https://github.com/ant-design/ant-design/pull/23192) +- 🐞 修复引用暗黑或紧凑主题时提示 `Variable is undefined` 的问题,并提供 `getThemeVariables` 方便获取对应主题变量。[#23171](https://github.com/ant-design/ant-design/pull/23171) +- 🐞 修复 PageHeader `title` 超长时布局被破坏的问题并优化响应式表现。[#23133](https://github.com/ant-design/ant-design/pull/23133) +- Tabs + - 🐞 修复 Tabs `@tabs-card-height` less 变量无效的问题。[#23168](https://github.com/ant-design/ant-design/pull/23168) + - 🐞 修复 Tabs 在 Safair 浏览器下无法显示的问题。[#23151](https://github.com/ant-design/ant-design/pull/23151) [@imhxc](https://github.com/imhxc) +- Table + - 🐞 修复 Table 固定列在 Safari 12 中不能固定的问题。[#23161](https://github.com/ant-design/ant-design/pull/23161) + - 🐞 修复 Table `summary` 在小尺寸下的内边距样式。[#23140](https://github.com/ant-design/ant-design/pull/23140) [@someyoungideas](https://github.com/someyoungideas) +- 🐞 修复 Select 不同尺寸下的对齐样式问题。[#23160](https://github.com/ant-design/ant-design/pull/23160) +- 🐞 修复 RangePicker 在 Input.Group 内的样式问题。[#23149](https://github.com/ant-design/ant-design/pull/23149) +- 🐞 修复 Pagination 缺少 `showTitle` TypeScript 定义的问题。[#23144](https://github.com/ant-design/ant-design/pull/23144) [@DongchengWang](https://github.com/DongchengWang) + ## 4.1.2 +`2020-04-10` + - Menu - 🐞 修复暗色 Menu 弹出菜单背景色为白色的问题。[#22981](https://github.com/ant-design/ant-design/pull/22981) [@AshoneA](https://github.com/AshoneA) - 🐞 修复 SubMenu 标题过长而导致被箭头图标部分覆盖的问题。[#23028](https://github.com/ant-design/ant-design/pull/23028) [@wwyx778](https://github.com/wwyx778) @@ -52,6 +102,8 @@ timeline: true ## 4.1.1 +`2020-04-05` + - 🐞 移除 Tabs 的内容区域的 focus 蓝色轮廓线。[#22752](https://github.com/ant-design/ant-design/pull/22752) [@MrHeer](https://github.com/MrHeer) - 🐞 修复 Input 前后缀添加弹出元素不能点击获得焦点的问题。[#22887](https://github.com/ant-design/ant-design/pull/22887) - Table diff --git a/components/__tests__/util/domHook.js b/components/__tests__/util/domHook.ts similarity index 79% rename from components/__tests__/util/domHook.js rename to components/__tests__/util/domHook.ts index dddc3ffc27..26f4a0532f 100644 --- a/components/__tests__/util/domHook.js +++ b/components/__tests__/util/domHook.ts @@ -1,6 +1,10 @@ const __NULL__ = { notExist: true }; -export function spyElementPrototypes(Element, properties) { +type ElementType

= { + prototype: P; +}; + +export function spyElementPrototypes

(Element: ElementType

, properties: P) { const propNames = Object.keys(properties); const originDescriptors = {}; @@ -51,7 +55,16 @@ export function spyElementPrototypes(Element, properties) { }; } -export function spyElementPrototype(Element, propName, property) { +type FunctionPropertyNames = { + [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; +}[keyof T] & + string; + +export function spyElementPrototype

>( + Element: ElementType

, + propName: K, + property: P[K], +) { return spyElementPrototypes(Element, { [propName]: property, }); diff --git a/components/affix/__tests__/Affix.test.js b/components/affix/__tests__/Affix.test.tsx similarity index 91% rename from components/affix/__tests__/Affix.test.js rename to components/affix/__tests__/Affix.test.tsx index 78c81d5c6b..1c34b576ef 100644 --- a/components/affix/__tests__/Affix.test.js +++ b/components/affix/__tests__/Affix.test.tsx @@ -7,9 +7,17 @@ import { spyElementPrototype } from '../../__tests__/util/domHook'; import rtlTest from '../../../tests/shared/rtlTest'; import { sleep } from '../../../tests/utils'; -const events = {}; +const events: any = {}; + +class AffixMounter extends React.Component<{ + offsetBottom?: number; + offsetTop?: number; + onTestUpdatePosition?(): void; +}> { + private container: HTMLDivElement; + + private affix: Affix; -class AffixMounter extends React.Component { componentDidMount() { this.container.addEventListener = jest.fn().mockImplementation((event, cb) => { events[event] = cb; @@ -47,7 +55,7 @@ describe('Affix Render', () => { let wrapper; let domMock; - const classRect = { + const classRect: any = { container: { top: 0, bottom: 100, @@ -135,9 +143,9 @@ describe('Affix Render', () => { describe('updatePosition when target changed', () => { it('function change', () => { document.body.innerHTML = '

'; - const container = document.querySelector('#id'); + const container = document.querySelector('#id') as HTMLDivElement; const getTarget = () => container; - wrapper = mount(); + wrapper = mount({null}); wrapper.setProps({ target: null }); expect(wrapper.instance().state.status).toBe(0); expect(wrapper.instance().state.affixStyle).toBe(undefined); @@ -153,7 +161,7 @@ describe('Affix Render', () => { const originLength = getObserverLength(); const getTarget = () => target; - wrapper = mount(); + wrapper = mount({null}); await sleep(50); expect(getObserverLength()).toBe(originLength + 1); diff --git a/components/affix/__tests__/__snapshots__/Affix.test.js.snap b/components/affix/__tests__/__snapshots__/Affix.test.tsx.snap similarity index 100% rename from components/affix/__tests__/__snapshots__/Affix.test.js.snap rename to components/affix/__tests__/__snapshots__/Affix.test.tsx.snap diff --git a/components/affix/__tests__/__snapshots__/demo.test.js.snap b/components/affix/__tests__/__snapshots__/demo.test.ts.snap similarity index 100% rename from components/affix/__tests__/__snapshots__/demo.test.js.snap rename to components/affix/__tests__/__snapshots__/demo.test.ts.snap diff --git a/components/affix/__tests__/demo.test.js b/components/affix/__tests__/demo.test.ts similarity index 100% rename from components/affix/__tests__/demo.test.js rename to components/affix/__tests__/demo.test.ts diff --git a/components/affix/index.tsx b/components/affix/index.tsx index 4e00fae97e..737565a63d 100644 --- a/components/affix/index.tsx +++ b/components/affix/index.tsx @@ -32,7 +32,7 @@ export interface AffixProps { target?: () => Window | HTMLElement | null; prefixCls?: string; className?: string; - children: React.ReactElement; + children: React.ReactNode; } enum AffixStatus { diff --git a/components/button/__tests__/__snapshots__/demo.test.js.snap b/components/button/__tests__/__snapshots__/demo.test.ts.snap similarity index 100% rename from components/button/__tests__/__snapshots__/demo.test.js.snap rename to components/button/__tests__/__snapshots__/demo.test.ts.snap diff --git a/components/button/__tests__/__snapshots__/index.test.js.snap b/components/button/__tests__/__snapshots__/type.test.tsx.snap similarity index 100% rename from components/button/__tests__/__snapshots__/index.test.js.snap rename to components/button/__tests__/__snapshots__/type.test.tsx.snap diff --git a/components/button/__tests__/demo.test.js b/components/button/__tests__/demo.test.ts similarity index 100% rename from components/button/__tests__/demo.test.js rename to components/button/__tests__/demo.test.ts diff --git a/components/button/__tests__/index.test.js b/components/button/__tests__/type.test.tsx similarity index 94% rename from components/button/__tests__/index.test.js rename to components/button/__tests__/type.test.tsx index 425326aca5..dcd4a8a52a 100644 --- a/components/button/__tests__/index.test.js +++ b/components/button/__tests__/type.test.tsx @@ -1,12 +1,12 @@ import React, { Component } from 'react'; import { mount, render } from 'enzyme'; -import renderer from 'react-test-renderer'; import { SearchOutlined } from '@ant-design/icons'; import Button from '..'; import ConfigProvider from '../../config-provider'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { sleep } from '../../../tests/utils'; +import { SizeType } from '../../config-provider/SizeContext'; describe('Button', () => { mountTest(Button); @@ -30,13 +30,14 @@ describe('Button', () => { }); it('mount correctly', () => { - expect(() => renderer.create()).not.toThrow(); + expect(() => mount()).not.toThrow(); }); it('warns if size is wrong', () => { const mockWarn = jest.fn(); jest.spyOn(console, 'warn').mockImplementation(mockWarn); - render(); + const size = ('who am I' as any) as SizeType; + render(); expect(mockWarn).toHaveBeenCalledTimes(1); expect(mockWarn.mock.calls[0][0]).toMatchObject({ message: 'unreachable case: "who am I"', @@ -74,7 +75,7 @@ describe('Button', () => { }); it('renders Chinese characters correctly in HOC', () => { - const Text = ({ children }) => {children}; + const Text = ({ children }: { children: React.ReactNode }) => {children}; const wrapper = mount( ); - expect(wrapper.type().__ANT_BUTTON).toBe(true); + expect((wrapper.type() as any).__ANT_BUTTON).toBe(true); }); it('should change loading state instantly by default', () => { @@ -189,7 +190,7 @@ describe('Button', () => { it('should has click wave effect', async () => { const wrapper = mount(); - wrapper.find('.ant-btn').getDOMNode().click(); + wrapper.find('.ant-btn').getDOMNode().click(); await new Promise(resolve => setTimeout(resolve, 0)); expect(wrapper.render()).toMatchSnapshot(); }); @@ -262,6 +263,7 @@ describe('Button', () => { throw new Error('Should not called!!!'); }, }); - wrapper.find('Button').instance().forceUpdate(); + + expect(wrapper.find('Button').instance()).toBe(null); }); }); diff --git a/components/button/button.tsx b/components/button/button.tsx index e0c3dbfc3b..84bc381f0f 100644 --- a/components/button/button.tsx +++ b/components/button/button.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import omit from 'omit.js'; import Group from './button-group'; -import { ConfigContext, ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; import Wave from '../_util/wave'; import { Omit, tuple } from '../_util/type'; import warning from '../_util/warning'; @@ -104,72 +104,57 @@ export type NativeButtonProps = { export type ButtonProps = Partial; -interface ButtonState { - loading?: boolean | { delay?: number }; - hasTwoCNChar: boolean; +interface ButtonTypeProps extends React.FC { + Group: typeof Group; + __ANT_BUTTON: boolean; } -class Button extends React.Component { - static Group: typeof Group; +const Button: ButtonTypeProps = ({ ...props }) => { + const [loading, setLoading] = React.useState(props.loading); + const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false); + const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext); + const buttonRef = React.createRef(); + let delayTimeout: number; - static __ANT_BUTTON = true; - - static contextType = ConfigContext; - - static defaultProps = { - loading: false, - ghost: false, - block: false, - htmlType: 'button' as ButtonProps['htmlType'], + const isNeedInserted = () => { + const { icon, children, type } = props; + return React.Children.count(children) === 1 && !icon && type !== 'link'; }; - private delayTimeout: number; - - private buttonNode: HTMLElement | null; - - constructor(props: ButtonProps) { - super(props); - this.state = { - loading: props.loading, - hasTwoCNChar: false, - }; - } - - componentDidMount() { - this.fixTwoCNChar(); - } - - componentDidUpdate(prevProps: ButtonProps) { - this.fixTwoCNChar(); - - if (prevProps.loading && typeof prevProps.loading !== 'boolean') { - clearTimeout(this.delayTimeout); + const fixTwoCNChar = () => { + // Fix for HOC usage like + if (!buttonRef || !buttonRef.current || autoInsertSpaceInButton === false) { + return; } - - const { loading } = this.props; - if (loading && typeof loading !== 'boolean' && loading.delay) { - this.delayTimeout = window.setTimeout(() => { - this.setState({ loading }); - }, loading.delay); - } else if (prevProps.loading !== loading) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ loading }); + const buttonText = buttonRef.current.textContent; + if (isNeedInserted() && isTwoCNChar(buttonText)) { + if (!hasTwoCNChar) { + setHasTwoCNChar(true); + } + } else if (hasTwoCNChar) { + setHasTwoCNChar(false); } - } - - componentWillUnmount() { - if (this.delayTimeout) { - clearTimeout(this.delayTimeout); - } - } - - saveButtonRef = (node: HTMLElement | null) => { - this.buttonNode = node; }; - handleClick: React.MouseEventHandler = e => { - const { loading } = this.state; - const { onClick } = this.props; + React.useEffect(() => { + if (props.loading && typeof props.loading !== 'boolean') { + clearTimeout(delayTimeout); + } + if (props.loading && typeof props.loading !== 'boolean' && props.loading.delay) { + delayTimeout = window.setTimeout(() => { + setLoading(props.loading); + }, props.loading.delay); + } else if (props.loading !== loading) { + setLoading(props.loading); + } + }, [props.loading]); + + React.useEffect(() => { + fixTwoCNChar(); + }, [buttonRef]); + + const handleClick = (e: React.MouseEvent) => { + const { onClick } = props; if (loading) { return; } @@ -178,143 +163,117 @@ class Button extends React.Component { } }; - fixTwoCNChar() { - const { autoInsertSpaceInButton }: ConfigConsumerProps = this.context; + return ( + + {size => { + const { + prefixCls: customizePrefixCls, + type, + danger, + shape, + size: customizeSize, + className, + children, + icon, + ghost, + block, + ...rest + } = props; - // Fix for HOC usage like - if (!this.buttonNode || autoInsertSpaceInButton === false) { - return; - } - const buttonText = this.buttonNode.textContent; - if (this.isNeedInserted() && isTwoCNChar(buttonText)) { - if (!this.state.hasTwoCNChar) { - this.setState({ - hasTwoCNChar: true, + warning( + !(typeof icon === 'string' && icon.length > 2), + 'Button', + `\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`, + ); + + const prefixCls = getPrefixCls('btn', customizePrefixCls); + const autoInsertSpace = autoInsertSpaceInButton !== false; + + // large => lg + // small => sm + let sizeCls = ''; + switch (customizeSize || size) { + case 'large': + sizeCls = 'lg'; + break; + case 'small': + sizeCls = 'sm'; + break; + default: + break; + } + + const iconType = loading ? 'loading' : icon; + + const classes = classNames(prefixCls, className, { + [`${prefixCls}-${type}`]: type, + [`${prefixCls}-${shape}`]: shape, + [`${prefixCls}-${sizeCls}`]: sizeCls, + [`${prefixCls}-icon-only`]: !children && children !== 0 && iconType, + [`${prefixCls}-background-ghost`]: ghost, + [`${prefixCls}-loading`]: loading, + [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace, + [`${prefixCls}-block`]: block, + [`${prefixCls}-dangerous`]: !!danger, + [`${prefixCls}-rtl`]: direction === 'rtl', }); - } - } else if (this.state.hasTwoCNChar) { - this.setState({ - hasTwoCNChar: false, - }); - } - } - isNeedInserted() { - const { icon, children, type } = this.props; - return React.Children.count(children) === 1 && !icon && type !== 'link'; - } - - render() { - const { getPrefixCls, autoInsertSpaceInButton, direction }: ConfigConsumerProps = this.context; - - return ( - - {size => { - const { - prefixCls: customizePrefixCls, - type, - danger, - shape, - size: customizeSize, - className, - children, - icon, - ghost, - block, - ...rest - } = this.props; - const { loading, hasTwoCNChar } = this.state; - - warning( - !(typeof icon === 'string' && icon.length > 2), - 'Button', - `\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`, + const iconNode = + icon && !loading ? ( + icon + ) : ( + ); - const prefixCls = getPrefixCls('btn', customizePrefixCls); - const autoInsertSpace = autoInsertSpaceInButton !== false; + const kids = + children || children === 0 + ? spaceChildren(children, isNeedInserted() && autoInsertSpace) + : null; - // large => lg - // small => sm - let sizeCls = ''; - switch (customizeSize || size) { - case 'large': - sizeCls = 'lg'; - break; - case 'small': - sizeCls = 'sm'; - break; - default: - break; - } - - const iconType = loading ? 'loading' : icon; - - const classes = classNames(prefixCls, className, { - [`${prefixCls}-${type}`]: type, - [`${prefixCls}-${shape}`]: shape, - [`${prefixCls}-${sizeCls}`]: sizeCls, - [`${prefixCls}-icon-only`]: !children && children !== 0 && iconType, - [`${prefixCls}-background-ghost`]: ghost, - [`${prefixCls}-loading`]: loading, - [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace, - [`${prefixCls}-block`]: block, - [`${prefixCls}-dangerous`]: !!danger, - [`${prefixCls}-rtl`]: direction === 'rtl', - }); - - const iconNode = - icon && !loading ? ( - icon - ) : ( - - ); - - const kids = - children || children === 0 - ? spaceChildren(children, this.isNeedInserted() && autoInsertSpace) - : null; - - const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']); - if (linkButtonRestProps.href !== undefined) { - return ( - - {iconNode} - {kids} - - ); - } - - // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`. - const { htmlType, ...otherProps } = rest as NativeButtonProps; - - const buttonNode = ( - + ); + } - if (type === 'link') { - return buttonNode; - } + // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`. + const { htmlType, ...otherProps } = rest as NativeButtonProps; - return {buttonNode}; - }} - - ); - } -} + const buttonNode = ( + + ); + + if (type === 'link') { + return buttonNode; + } + + return {buttonNode}; + }} + + ); +}; + +Button.defaultProps = { + loading: false, + ghost: false, + block: false, + htmlType: 'button' as ButtonProps['htmlType'], +}; + +Button.Group = Group; +Button.__ANT_BUTTON = true; export default Button; diff --git a/components/button/index.tsx b/components/button/index.tsx index 1f3b6d5996..3fd65352f3 100644 --- a/components/button/index.tsx +++ b/components/button/index.tsx @@ -1,9 +1,7 @@ import Button from './button'; -import ButtonGroup from './button-group'; export { ButtonProps, ButtonShape, ButtonType } from './button'; export { ButtonGroupProps } from './button-group'; export { SizeType as ButtonSize } from '../config-provider/SizeContext'; -Button.Group = ButtonGroup; export default Button; diff --git a/components/card/index.en-US.md b/components/card/index.en-US.md index 6ef0027d47..dc72371e2c 100644 --- a/components/card/index.en-US.md +++ b/components/card/index.en-US.md @@ -37,7 +37,7 @@ A card can be used to display content related to a single subject. The content c | title | Card title | string\|ReactNode | - | | | type | Card style type, can be set to `inner` or not set | string | - | | | onTabChange | Callback when tab is switched | (key) => void | - | | -| tabProps | [Tabs](https://ant.design/components/tabs/#Tabs) | - | - | | +| tabProps | [Tabs](/components/tabs/#Tabs) | - | - | | ### Card.Grid diff --git a/components/card/index.zh-CN.md b/components/card/index.zh-CN.md index 289c4a79af..a62bc1a63e 100644 --- a/components/card/index.zh-CN.md +++ b/components/card/index.zh-CN.md @@ -38,7 +38,7 @@ cols: 1 | title | 卡片标题 | string\|ReactNode | - | | | type | 卡片类型,可设置为 `inner` 或 不设置 | string | - | | | onTabChange | 页签切换的回调 | (key) => void | - | | -| tabProps | [Tabs](https://ant.design/components/tabs-cn/#Tabs) | - | - | | +| tabProps | [Tabs](/components/tabs/#Tabs) | - | - | | ### Card.Grid diff --git a/components/checkbox/Checkbox.tsx b/components/checkbox/Checkbox.tsx index 6628a77216..6c4cac4843 100644 --- a/components/checkbox/Checkbox.tsx +++ b/components/checkbox/Checkbox.tsx @@ -63,7 +63,7 @@ class Checkbox extends React.PureComponent { warning( 'checked' in this.props || this.context || !('value' in this.props), 'Checkbox', - '`value` is not validate prop, do you mean `checked`?', + '`value` is not a valid prop, do you mean `checked`?', ); } diff --git a/components/checkbox/__tests__/checkbox.test.js b/components/checkbox/__tests__/checkbox.test.js index e9d5437d01..c5e45181cd 100644 --- a/components/checkbox/__tests__/checkbox.test.js +++ b/components/checkbox/__tests__/checkbox.test.js @@ -30,7 +30,7 @@ describe('Checkbox', () => { const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); mount(); expect(errorSpy).toHaveBeenCalledWith( - 'Warning: [antd: Checkbox] `value` is not validate prop, do you mean `checked`?', + 'Warning: [antd: Checkbox] `value` is not a valid prop, do you mean `checked`?', ); errorSpy.mockRestore(); }); diff --git a/components/config-provider/__tests__/__snapshots__/components.test.js.snap b/components/config-provider/__tests__/__snapshots__/components.test.js.snap index c6e9cf2c4a..a933644a7c 100644 --- a/components/config-provider/__tests__/__snapshots__/components.test.js.snap +++ b/components/config-provider/__tests__/__snapshots__/components.test.js.snap @@ -16,6 +16,38 @@ exports[`ConfigProvider components Alert configProvider 1`] = `
`; +exports[`ConfigProvider components Alert configProvider componentSize large 1`] = ` +
+ + Bamboo is Little Light + + +
+`; + +exports[`ConfigProvider components Alert configProvider componentSize middle 1`] = ` +
+ + Bamboo is Little Light + + +
+`; + exports[`ConfigProvider components Alert normal 1`] = `
`; +exports[`ConfigProvider components Anchor configProvider componentSize large 1`] = ` +
+
+
+
+
+ +
+ +
+
+
+
+`; + +exports[`ConfigProvider components Anchor configProvider componentSize middle 1`] = ` +
+
+
+
+
+ +
+ +
+
+
+
+`; + exports[`ConfigProvider components Anchor normal 1`] = `
`; +exports[`ConfigProvider components AutoComplete configProvider componentSize large 1`] = ` + +`; + +exports[`ConfigProvider components AutoComplete configProvider componentSize middle 1`] = ` + +`; + exports[`ConfigProvider components AutoComplete normal 1`] = `