mirror of
https://github.com/ant-design/ant-design.git
synced 2025-07-24 23:46:51 +08:00
Merge pull request #23377 from ant-design/master
chore: Merge master into feature
This commit is contained in:
commit
fabec4831c
@ -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();
|
||||
|
11
.jest.js
11
.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',
|
||||
|
@ -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'],
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,10 @@
|
||||
const __NULL__ = { notExist: true };
|
||||
|
||||
export function spyElementPrototypes(Element, properties) {
|
||||
type ElementType<P> = {
|
||||
prototype: P;
|
||||
};
|
||||
|
||||
export function spyElementPrototypes<P extends {}>(Element: ElementType<P>, 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<T> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
|
||||
}[keyof T] &
|
||||
string;
|
||||
|
||||
export function spyElementPrototype<P extends {}, K extends FunctionPropertyNames<P>>(
|
||||
Element: ElementType<P>,
|
||||
propName: K,
|
||||
property: P[K],
|
||||
) {
|
||||
return spyElementPrototypes(Element, {
|
||||
[propName]: property,
|
||||
});
|
@ -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 = '<div id="mounter" />';
|
||||
const container = document.querySelector('#id');
|
||||
const container = document.querySelector('#id') as HTMLDivElement;
|
||||
const getTarget = () => container;
|
||||
wrapper = mount(<Affix target={getTarget} />);
|
||||
wrapper = mount(<Affix target={getTarget}>{null}</Affix>);
|
||||
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(<Affix target={getTarget} />);
|
||||
wrapper = mount(<Affix target={getTarget}>{null}</Affix>);
|
||||
await sleep(50);
|
||||
|
||||
expect(getObserverLength()).toBe(originLength + 1);
|
@ -32,7 +32,7 @@ export interface AffixProps {
|
||||
target?: () => Window | HTMLElement | null;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
children: React.ReactElement;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
enum AffixStatus {
|
||||
|
@ -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(<Button>Follow</Button>)).not.toThrow();
|
||||
expect(() => mount(<Button>Follow</Button>)).not.toThrow();
|
||||
});
|
||||
|
||||
it('warns if size is wrong', () => {
|
||||
const mockWarn = jest.fn();
|
||||
jest.spyOn(console, 'warn').mockImplementation(mockWarn);
|
||||
render(<Button.Group size="who am I" />);
|
||||
const size = ('who am I' as any) as SizeType;
|
||||
render(<Button.Group size={size} />);
|
||||
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 }) => <span>{children}</span>;
|
||||
const Text = ({ children }: { children: React.ReactNode }) => <span>{children}</span>;
|
||||
const wrapper = mount(
|
||||
<Button>
|
||||
<Text>按钮</Text>
|
||||
@ -110,7 +111,7 @@ describe('Button', () => {
|
||||
|
||||
it('have static property for type detecting', () => {
|
||||
const wrapper = mount(<Button>Button Text</Button>);
|
||||
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(<Button type="primary">button</Button>);
|
||||
wrapper.find('.ant-btn').getDOMNode().click();
|
||||
wrapper.find('.ant-btn').getDOMNode<HTMLButtonElement>().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);
|
||||
});
|
||||
});
|
@ -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<AnchorButtonProps & NativeButtonProps>;
|
||||
|
||||
interface ButtonState {
|
||||
loading?: boolean | { delay?: number };
|
||||
hasTwoCNChar: boolean;
|
||||
interface ButtonTypeProps extends React.FC<ButtonProps> {
|
||||
Group: typeof Group;
|
||||
__ANT_BUTTON: boolean;
|
||||
}
|
||||
|
||||
class Button extends React.Component<ButtonProps, ButtonState> {
|
||||
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<HTMLButtonElement>();
|
||||
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 <FormatMessage />
|
||||
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<HTMLButtonElement | HTMLAnchorElement> = 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<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
|
||||
const { onClick } = props;
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
@ -178,143 +163,117 @@ class Button extends React.Component<ButtonProps, ButtonState> {
|
||||
}
|
||||
};
|
||||
|
||||
fixTwoCNChar() {
|
||||
const { autoInsertSpaceInButton }: ConfigConsumerProps = this.context;
|
||||
return (
|
||||
<SizeContext.Consumer>
|
||||
{size => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
type,
|
||||
danger,
|
||||
shape,
|
||||
size: customizeSize,
|
||||
className,
|
||||
children,
|
||||
icon,
|
||||
ghost,
|
||||
block,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
// Fix for HOC usage like <FormatMessage />
|
||||
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 (
|
||||
<SizeContext.Consumer>
|
||||
{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
|
||||
) : (
|
||||
<LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={loading} />
|
||||
);
|
||||
|
||||
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
|
||||
) : (
|
||||
<LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={loading} />
|
||||
);
|
||||
|
||||
const kids =
|
||||
children || children === 0
|
||||
? spaceChildren(children, this.isNeedInserted() && autoInsertSpace)
|
||||
: null;
|
||||
|
||||
const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']);
|
||||
if (linkButtonRestProps.href !== undefined) {
|
||||
return (
|
||||
<a
|
||||
{...linkButtonRestProps}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveButtonRef}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 = (
|
||||
<button
|
||||
{...(omit(otherProps, ['loading']) as NativeButtonProps)}
|
||||
type={htmlType}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveButtonRef}
|
||||
>
|
||||
const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']);
|
||||
if (linkButtonRestProps.href !== undefined) {
|
||||
return (
|
||||
<a {...linkButtonRestProps} className={classes} onClick={handleClick} ref={buttonRef}>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</button>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
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 <Wave>{buttonNode}</Wave>;
|
||||
}}
|
||||
</SizeContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
const buttonNode = (
|
||||
<button
|
||||
{...(omit(otherProps, ['loading']) as NativeButtonProps)}
|
||||
type={htmlType}
|
||||
className={classes}
|
||||
onClick={handleClick}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (type === 'link') {
|
||||
return buttonNode;
|
||||
}
|
||||
|
||||
return <Wave>{buttonNode}</Wave>;
|
||||
}}
|
||||
</SizeContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
loading: false,
|
||||
ghost: false,
|
||||
block: false,
|
||||
htmlType: 'button' as ButtonProps['htmlType'],
|
||||
};
|
||||
|
||||
Button.Group = Group;
|
||||
Button.__ANT_BUTTON = true;
|
||||
|
||||
export default Button;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -63,7 +63,7 @@ class Checkbox extends React.PureComponent<CheckboxProps, {}> {
|
||||
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`?',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ describe('Checkbox', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
mount(<Checkbox value />);
|
||||
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();
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -82,6 +82,26 @@ describe('ConfigProvider', () => {
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('configProvider componentSize large', () => {
|
||||
expect(
|
||||
render(
|
||||
<ConfigProvider componentSize="large" prefixCls="config">
|
||||
{renderComponent({})}
|
||||
</ConfigProvider>,
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('configProvider componentSize middle', () => {
|
||||
expect(
|
||||
render(
|
||||
<ConfigProvider componentSize="middle" prefixCls="config">
|
||||
{renderComponent({})}
|
||||
</ConfigProvider>,
|
||||
),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,12 @@ import zhCN from '../../locale/zh_CN';
|
||||
import enUS from '../../locale/en_US';
|
||||
import TimePicker from '../../time-picker';
|
||||
import Modal from '../../modal';
|
||||
import Form from '../../form';
|
||||
|
||||
const delay = (timeout = 0) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(resolve, timeout);
|
||||
});
|
||||
|
||||
describe('ConfigProvider.Locale', () => {
|
||||
function $$(className) {
|
||||
@ -104,4 +110,41 @@ describe('ConfigProvider.Locale', () => {
|
||||
testLocale(wrapper);
|
||||
});
|
||||
});
|
||||
|
||||
describe('form validateMessages', () => {
|
||||
const wrapperComponent = ({ validateMessages }) =>
|
||||
mount(
|
||||
<ConfigProvider locale={zhCN} form={{ validateMessages }}>
|
||||
<Form initialValues={{ age: 18 }}>
|
||||
<Form.Item name="test" label="姓名" rules={[{ required: true }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
<Form.Item name="age" label="年龄" rules={[{ type: 'number', len: 17 }]}>
|
||||
<input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
|
||||
it('set locale zhCN', async () => {
|
||||
const wrapper = wrapperComponent({});
|
||||
|
||||
wrapper.find('form').simulate('submit');
|
||||
await delay(50);
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('请输入姓名');
|
||||
});
|
||||
|
||||
it('set locale zhCN and set form validateMessages one item, other use default message', async () => {
|
||||
const wrapper = wrapperComponent({ validateMessages: { required: '必须' } });
|
||||
|
||||
wrapper.find('form').simulate('submit');
|
||||
await delay(50);
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('必须');
|
||||
expect(wrapper.find('.ant-form-item-explain').last().text()).toEqual('年龄必须等于17');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -46,6 +46,9 @@ const FormSizeDemo = () => {
|
||||
<div className="example">
|
||||
<Input />
|
||||
</div>
|
||||
<div className="example">
|
||||
<Input.Search />
|
||||
</div>
|
||||
<div className="example">
|
||||
<Select defaultValue="demo" options={[{ value: 'demo' }]} />
|
||||
</div>
|
||||
|
@ -93,10 +93,17 @@ class ConfigProvider extends React.Component<ConfigProviderProps> {
|
||||
let childNode = children;
|
||||
|
||||
// Additional Form provider
|
||||
let validateMessages: ValidateMessages = {};
|
||||
|
||||
if (locale && locale.Form && locale.Form.defaultValidateMessages) {
|
||||
validateMessages = locale.Form.defaultValidateMessages;
|
||||
}
|
||||
if (form && form.validateMessages) {
|
||||
childNode = (
|
||||
<RcFormProvider validateMessages={form.validateMessages}>{children}</RcFormProvider>
|
||||
);
|
||||
validateMessages = { ...validateMessages, ...form.validateMessages };
|
||||
}
|
||||
|
||||
if (Object.keys(validateMessages).length > 0) {
|
||||
childNode = <RcFormProvider validateMessages={validateMessages}>{children}</RcFormProvider>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -68,6 +68,8 @@
|
||||
&-submenu-popup {
|
||||
position: absolute;
|
||||
z-index: @zindex-dropdown;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
> .@{dropdown-prefix-cls}-menu {
|
||||
transform-origin: 0 0;
|
||||
|
@ -24,7 +24,7 @@ Empty state placeholder.
|
||||
| --- | --- | --- | --- | --- |
|
||||
| description | Customize description | string \| ReactNode | - | |
|
||||
| imageStyle | style of image | CSSProperties | - | |
|
||||
| image | Customize image. Will tread as image url when string provided. | string \| ReactNode | `Empty.PRESENTED_IMAGE_DEFAULT` | |
|
||||
| image | Customize image. Will treat as image url when string provided. | string \| ReactNode | `Empty.PRESENTED_IMAGE_DEFAULT` | |
|
||||
|
||||
## Built-in images
|
||||
|
||||
|
@ -24,22 +24,17 @@ type ChildrenType = RenderChildren | React.ReactNode;
|
||||
interface MemoInputProps {
|
||||
value: any;
|
||||
update: number;
|
||||
children: any;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const MemoInput = React.memo<MemoInputProps>(
|
||||
({ children }) => {
|
||||
return children;
|
||||
},
|
||||
const MemoInput = React.memo(
|
||||
({ children }: MemoInputProps) => children as JSX.Element,
|
||||
(prev, next) => {
|
||||
return prev.value === next.value && prev.update === next.update;
|
||||
},
|
||||
);
|
||||
|
||||
export interface FormItemProps
|
||||
extends FormItemLabelProps,
|
||||
FormItemInputProps,
|
||||
Omit<RcFieldProps, 'children'> {
|
||||
export interface FormItemProps extends FormItemLabelProps, FormItemInputProps, RcFieldProps {
|
||||
prefixCls?: string;
|
||||
noStyle?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
@ -129,7 +124,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
|
||||
fieldId?: string,
|
||||
meta?: Meta,
|
||||
isRequired?: boolean,
|
||||
): any {
|
||||
): React.ReactNode {
|
||||
if (noStyle) {
|
||||
return baseChildren;
|
||||
}
|
||||
@ -226,7 +221,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
|
||||
updateRef.current += 1;
|
||||
|
||||
if (!hasName && !isRenderProps && !dependencies) {
|
||||
return renderLayout(children);
|
||||
return renderLayout(children) as JSX.Element;
|
||||
}
|
||||
|
||||
const variables: Record<string, string> = {};
|
||||
@ -304,7 +299,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
|
||||
);
|
||||
} else if (React.isValidElement(children)) {
|
||||
warning(
|
||||
(children.props as any).defaultValue === undefined,
|
||||
children.props.defaultValue === undefined,
|
||||
'Form.Item',
|
||||
'`defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.',
|
||||
);
|
||||
@ -337,7 +332,7 @@ function FormItem(props: FormItemProps): React.ReactElement {
|
||||
'Form.Item',
|
||||
'`name` is only used for validate React element. If you are using Form.Item as layout display, please remove `name` instead.',
|
||||
);
|
||||
childNode = children as any;
|
||||
childNode = children;
|
||||
}
|
||||
|
||||
return renderLayout(childNode, fieldId, meta, isRequired);
|
||||
|
@ -6206,7 +6206,29 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
@ -6360,6 +6382,27 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
aria-label="eye-invisible"
|
||||
class="anticon anticon-eye-invisible ant-input-password-icon"
|
||||
|
@ -585,9 +585,9 @@ describe('Form', () => {
|
||||
);
|
||||
|
||||
wrapper.find('form').simulate('submit');
|
||||
await delay(50);
|
||||
await delay(100);
|
||||
wrapper.update();
|
||||
|
||||
await delay(100);
|
||||
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('Bamboo is good!');
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
|
||||
import * as React from 'react';
|
||||
import Form from '..';
|
||||
import Input from '../../input';
|
||||
@ -17,5 +15,3 @@ describe('Form.typescript', () => {
|
||||
expect(form).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
/* eslint-enable */
|
||||
|
@ -25,13 +25,13 @@ High performance Form component with data scope management. Including data colle
|
||||
| hideRequiredMark | Hide required mark for all form items | boolean | false |
|
||||
| initialValues | Set value by Form initialization or reset | object | - |
|
||||
| labelAlign | text align of label of all items | `left` \| `right` | `right` |
|
||||
| labelCol | label layout, like `<Col>` component. Set `span` `offset` value like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` | [object](https://ant.design/components/grid/#Col) | - |
|
||||
| labelCol | label layout, like `<Col>` component. Set `span` `offset` value like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` | [object](/components/grid/#Col) | - |
|
||||
| layout | Form layout | `horizontal` \| `vertical` \| `inline` | `horizontal` |
|
||||
| name | Form name. Will be the prefix of Field `id` | string | - |
|
||||
| scrollToFirstError | Auto scroll to first failed field when submit | false | - |
|
||||
| size | Set field component size (antd components only) | `small` \| `middle` \| `large` | - |
|
||||
| validateMessages | Validation prompt template, description [see below](#validateMessages) | [ValidateMessages](https://github.com/react-component/field-form/blob/master/src/utils/messages.ts) | - |
|
||||
| wrapperCol | The layout for input controls, same as `labelCol` | [object](https://ant.design/components/grid/#Col) | - |
|
||||
| wrapperCol | The layout for input controls, same as `labelCol` | [object](/components/grid/#Col) | - |
|
||||
| onFinish | Trigger after submitting the form and verifying data successfully | Function(values) | - |
|
||||
| onFinishFailed | Trigger after submitting the form and verifying data failed | Function({ values, errorFields, outOfDate }) | - |
|
||||
| onFieldsChange | Trigger when field updated | Function(changedFields, allFields) | - |
|
||||
@ -84,7 +84,7 @@ Form field component for data bidirectional binding, validation, layout, and so
|
||||
| labelCol | The layout of label. You can set `span` `offset` to something like `{span: 3, offset: 12}` or `sm: {span: 3, offset: 12}` same as with `<Col>`. You can set `labelCol` on Form. If both exists, use Item first | [object](/components/grid/#Col) | - | |
|
||||
| name | Field name, support array | [NamePath](#NamePath) | - | |
|
||||
| normalize | Normalize value from component value before passing to Form instance | (value, prevValue, prevValues) => any | - | |
|
||||
| required | Whether provided or not, it will be generated by the validation rule | boolean | false | |
|
||||
| required | Display required style. It will be generated by the validation rule | boolean | false | |
|
||||
| rules | Rules for field validation. Click [here](#components-form-demo-basic) to see an example | [Rule](#Rule)[] | - | |
|
||||
| shouldUpdate | Custom field update logic. See [below](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false | |
|
||||
| trigger | When to collect the value of children node | string | onChange | |
|
||||
|
@ -26,13 +26,13 @@ title: Form
|
||||
| hideRequiredMark | 隐藏所有表单项的必选标记 | boolean | false |
|
||||
| initialValues | 表单默认值,只有初始化以及重置时生效 | object | - |
|
||||
| labelAlign | label 标签的文本对齐方式 | `left` \| `right` | `right` |
|
||||
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}` | [object](https://ant.design/components/grid/#Col) | - |
|
||||
| labelCol | label 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}` | [object](/components/grid/#Col) | - |
|
||||
| layout | 表单布局 | `horizontal` \| `vertical` \| `inline` | `horizontal` |
|
||||
| name | 表单名称,会作为表单字段 `id` 前缀使用 | string | - |
|
||||
| scrollToFirstError | 提交失败自动滚动到第一个错误字段 | false | - |
|
||||
| size | 设置字段组件的尺寸(仅限 antd 组件) | `small` \| `middle` \| `large` | - |
|
||||
| validateMessages | 验证提示模板,说明[见下](#validateMessages) | [ValidateMessages](https://github.com/react-component/field-form/blob/master/src/utils/messages.ts) | - |
|
||||
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](https://ant.design/components/grid/#Col) | - |
|
||||
| wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | [object](/components/grid/#Col) | - |
|
||||
| onFinish | 提交表单且数据验证成功后回调事件 | Function(values) | - |
|
||||
| onFinishFailed | 提交表单且数据验证失败后回调事件 | Function({ values, errorFields, outOfDate }) | - |
|
||||
| onFieldsChange | 字段更新时触发回调事件 | Function(changedFields, allFields) | - |
|
||||
@ -85,7 +85,7 @@ const validateMessages = {
|
||||
| labelCol | `label` 标签布局,同 `<Col>` 组件,设置 `span` `offset` 值,如 `{span: 3, offset: 12}` 或 `sm: {span: 3, offset: 12}`。你可以通过 Form 的 `labelCol` 进行统一设置。当和 Form 同时设置时,以 Item 为准 | [object](/components/grid/#Col) | - | |
|
||||
| name | 字段名,支持数组 | [NamePath](#NamePath) | - | |
|
||||
| normalize | 组件获取值后进行转换,再放入 Form 中 | (value, prevValue, prevValues) => any | - | |
|
||||
| required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false | |
|
||||
| required | 必填样式设置。如不设置,则会根据校验规则自动生成 | boolean | false | |
|
||||
| rules | 校验规则,设置字段的校验逻辑。点击[此处](#components-form-demo-basic)查看示例 | [Rule](#Rule)[] | - | |
|
||||
| shouldUpdate | 自定义字段更新逻辑,说明[见下](#shouldUpdate) | boolean \| (prevValue, curValue) => boolean | false | |
|
||||
| trigger | 设置收集字段值变更的时机 | string | onChange | |
|
||||
|
@ -36,6 +36,10 @@
|
||||
.@{form-prefix-cls}-vertical {
|
||||
.@{form-item-prefix-cls} {
|
||||
flex-direction: column;
|
||||
|
||||
&-label > label {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,21 +54,25 @@ class ClearableLabeledInput extends React.Component<ClearableInputProps> {
|
||||
|
||||
renderClearIcon(prefixCls: string) {
|
||||
const { allowClear, value, disabled, readOnly, inputType, handleReset } = this.props;
|
||||
if (
|
||||
!allowClear ||
|
||||
disabled ||
|
||||
readOnly ||
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === ''
|
||||
) {
|
||||
|
||||
if (!allowClear) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const needClear = !disabled && !readOnly && value;
|
||||
const className =
|
||||
inputType === ClearableInputType[0]
|
||||
? `${prefixCls}-textarea-clear-icon`
|
||||
: `${prefixCls}-clear-icon`;
|
||||
return <CloseCircleFilled onClick={handleReset} className={className} role="button" />;
|
||||
return (
|
||||
<CloseCircleFilled
|
||||
onClick={handleReset}
|
||||
className={classNames(className, {
|
||||
[`${className}-hidden`]: !needClear,
|
||||
})}
|
||||
role="button"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSuffix(prefixCls: string) {
|
||||
|
@ -4,6 +4,7 @@ import SearchOutlined from '@ant-design/icons/SearchOutlined';
|
||||
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
|
||||
import Input, { InputProps } from './Input';
|
||||
import Button from '../button';
|
||||
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
|
||||
export interface SearchProps extends InputProps {
|
||||
@ -65,13 +66,22 @@ export default class Search extends React.Component<SearchProps, any> {
|
||||
}
|
||||
|
||||
renderLoading = (prefixCls: string) => {
|
||||
const { enterButton, size } = this.props;
|
||||
const { enterButton, size: customizeSize } = this.props;
|
||||
|
||||
if (enterButton) {
|
||||
return (
|
||||
<Button className={`${prefixCls}-button`} type="primary" size={size} key="enterButton">
|
||||
<LoadingOutlined />
|
||||
</Button>
|
||||
<SizeContext.Consumer>
|
||||
{size => (
|
||||
<Button
|
||||
className={`${prefixCls}-button`}
|
||||
type="primary"
|
||||
size={customizeSize || size}
|
||||
key="enterButton"
|
||||
>
|
||||
<LoadingOutlined />
|
||||
</Button>
|
||||
)}
|
||||
</SizeContext.Consumer>
|
||||
);
|
||||
}
|
||||
return <LoadingOutlined className={`${prefixCls}-icon`} key="loadingIcon" />;
|
||||
@ -104,8 +114,8 @@ export default class Search extends React.Component<SearchProps, any> {
|
||||
return icon;
|
||||
};
|
||||
|
||||
renderAddonAfter = (prefixCls: string) => {
|
||||
const { enterButton, size, disabled, addonAfter, loading } = this.props;
|
||||
renderAddonAfter = (prefixCls: string, size: SizeType) => {
|
||||
const { enterButton, disabled, addonAfter, loading } = this.props;
|
||||
const btnClassName = `${prefixCls}-button`;
|
||||
|
||||
if (loading && enterButton) {
|
||||
@ -165,9 +175,9 @@ export default class Search extends React.Component<SearchProps, any> {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
inputPrefixCls: customizeInputPrefixCls,
|
||||
size,
|
||||
enterButton,
|
||||
className,
|
||||
size: customizeSize,
|
||||
...restProps
|
||||
} = this.props;
|
||||
|
||||
@ -177,32 +187,38 @@ export default class Search extends React.Component<SearchProps, any> {
|
||||
const prefixCls = getPrefixCls('input-search', customizePrefixCls);
|
||||
const inputPrefixCls = getPrefixCls('input', customizeInputPrefixCls);
|
||||
|
||||
let inputClassName;
|
||||
|
||||
if (enterButton) {
|
||||
inputClassName = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-enter-button`]: !!enterButton,
|
||||
[`${prefixCls}-${size}`]: !!size,
|
||||
});
|
||||
} else {
|
||||
inputClassName = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
}
|
||||
const getClassName = (size: SizeType) => {
|
||||
let inputClassName;
|
||||
if (enterButton) {
|
||||
inputClassName = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
[`${prefixCls}-enter-button`]: !!enterButton,
|
||||
[`${prefixCls}-${size}`]: !!size,
|
||||
});
|
||||
} else {
|
||||
inputClassName = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
}
|
||||
return inputClassName;
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
onPressEnter={this.onSearch}
|
||||
{...restProps}
|
||||
size={size}
|
||||
prefixCls={inputPrefixCls}
|
||||
addonAfter={this.renderAddonAfter(prefixCls)}
|
||||
suffix={this.renderSuffix(prefixCls)}
|
||||
onChange={this.onChange}
|
||||
ref={this.saveInput}
|
||||
className={inputClassName}
|
||||
/>
|
||||
<SizeContext.Consumer>
|
||||
{size => (
|
||||
<Input
|
||||
onPressEnter={this.onSearch}
|
||||
{...restProps}
|
||||
size={customizeSize || size}
|
||||
prefixCls={inputPrefixCls}
|
||||
addonAfter={this.renderAddonAfter(prefixCls, customizeSize || size)}
|
||||
suffix={this.renderSuffix(prefixCls)}
|
||||
onChange={this.onChange}
|
||||
ref={this.saveInput}
|
||||
className={getClassName(customizeSize || size)}
|
||||
/>
|
||||
)}
|
||||
</SizeContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -960,7 +960,29 @@ exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<br />
|
||||
<br />
|
||||
@ -971,6 +993,27 @@ exports[`renders ./components/input/demo/allowClear.md correctly 1`] = `
|
||||
class="ant-input"
|
||||
placeholder="textarea with clear icon"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
@ -48,7 +48,29 @@ exports[`Input allowClear should change type when click 2`] = `
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -63,7 +85,29 @@ exports[`Input allowClear should not show icon if defaultValue is undefined, nul
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -78,7 +122,29 @@ exports[`Input allowClear should not show icon if defaultValue is undefined, nul
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -93,7 +159,29 @@ exports[`Input allowClear should not show icon if defaultValue is undefined, nul
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -108,7 +196,29 @@ exports[`Input allowClear should not show icon if value is undefined, null or em
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -123,7 +233,29 @@ exports[`Input allowClear should not show icon if value is undefined, null or em
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -138,7 +270,29 @@ exports[`Input allowClear should not show icon if value is undefined, null or em
|
||||
/>
|
||||
<span
|
||||
class="ant-input-suffix"
|
||||
/>
|
||||
>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-clear-icon ant-input-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
|
@ -40,6 +40,27 @@ exports[`TextArea allowClear should change type when click 2`] = `
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -50,6 +71,27 @@ exports[`TextArea allowClear should not show icon if defaultValue is undefined,
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -60,6 +102,27 @@ exports[`TextArea allowClear should not show icon if defaultValue is undefined,
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -70,6 +133,27 @@ exports[`TextArea allowClear should not show icon if defaultValue is undefined,
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -80,6 +164,27 @@ exports[`TextArea allowClear should not show icon if value is undefined, null or
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -90,6 +195,27 @@ exports[`TextArea allowClear should not show icon if value is undefined, null or
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
@ -100,6 +226,27 @@ exports[`TextArea allowClear should not show icon if value is undefined, null or
|
||||
<textarea
|
||||
class="ant-input"
|
||||
/>
|
||||
<span
|
||||
aria-label="close-circle"
|
||||
class="anticon anticon-close-circle ant-input-textarea-clear-icon ant-input-textarea-clear-icon-hidden"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
|
@ -56,10 +56,7 @@ describe('Input', () => {
|
||||
describe('focus trigger warning', () => {
|
||||
it('not trigger', () => {
|
||||
const wrapper = mount(<Input suffix="bamboo" />);
|
||||
wrapper
|
||||
.find('input')
|
||||
.instance()
|
||||
.focus();
|
||||
wrapper.find('input').instance().focus();
|
||||
wrapper.setProps({
|
||||
suffix: 'light',
|
||||
});
|
||||
@ -67,10 +64,7 @@ describe('Input', () => {
|
||||
});
|
||||
it('trigger warning', () => {
|
||||
const wrapper = mount(<Input />, { attachTo: document.body });
|
||||
wrapper
|
||||
.find('input')
|
||||
.instance()
|
||||
.focus();
|
||||
wrapper.find('input').instance().focus();
|
||||
wrapper.setProps({
|
||||
suffix: 'light',
|
||||
});
|
||||
@ -129,10 +123,7 @@ describe('Input allowClear', () => {
|
||||
wrapper.find('input').simulate('change', { target: { value: '111' } });
|
||||
expect(wrapper.find('input').getDOMNode().value).toEqual('111');
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
wrapper
|
||||
.find('.ant-input-clear-icon')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.find('input').getDOMNode().value).toEqual('');
|
||||
});
|
||||
@ -141,7 +132,7 @@ describe('Input allowClear', () => {
|
||||
const wrappers = [null, undefined, ''].map(val => mount(<Input allowClear value={val} />));
|
||||
wrappers.forEach(wrapper => {
|
||||
expect(wrapper.find('input').getDOMNode().value).toEqual('');
|
||||
expect(wrapper.find('.ant-input-clear-icon').exists()).toEqual(false);
|
||||
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@ -152,7 +143,7 @@ describe('Input allowClear', () => {
|
||||
);
|
||||
wrappers.forEach(wrapper => {
|
||||
expect(wrapper.find('input').getDOMNode().value).toEqual('');
|
||||
expect(wrapper.find('.ant-input-clear-icon').exists()).toEqual(false);
|
||||
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@ -165,18 +156,10 @@ describe('Input allowClear', () => {
|
||||
argumentEventObjectValue = e.target.value;
|
||||
};
|
||||
const wrapper = mount(<Input allowClear defaultValue="111" onChange={onChange} />);
|
||||
wrapper
|
||||
.find('.ant-input-clear-icon')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
|
||||
expect(argumentEventObject.type).toBe('click');
|
||||
expect(argumentEventObjectValue).toBe('');
|
||||
expect(
|
||||
wrapper
|
||||
.find('input')
|
||||
.at(0)
|
||||
.getDOMNode().value,
|
||||
).toBe('');
|
||||
expect(wrapper.find('input').at(0).getDOMNode().value).toBe('');
|
||||
});
|
||||
|
||||
it('should trigger event correctly on controlled mode', () => {
|
||||
@ -187,39 +170,23 @@ describe('Input allowClear', () => {
|
||||
argumentEventObjectValue = e.target.value;
|
||||
};
|
||||
const wrapper = mount(<Input allowClear value="111" onChange={onChange} />);
|
||||
wrapper
|
||||
.find('.ant-input-clear-icon')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
|
||||
expect(argumentEventObject.type).toBe('click');
|
||||
expect(argumentEventObjectValue).toBe('');
|
||||
expect(
|
||||
wrapper
|
||||
.find('input')
|
||||
.at(0)
|
||||
.getDOMNode().value,
|
||||
).toBe('111');
|
||||
expect(wrapper.find('input').at(0).getDOMNode().value).toBe('111');
|
||||
});
|
||||
|
||||
it('should focus input after clear', () => {
|
||||
const wrapper = mount(<Input allowClear defaultValue="111" />, { attachTo: document.body });
|
||||
wrapper
|
||||
.find('.ant-input-clear-icon')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
expect(document.activeElement).toBe(
|
||||
wrapper
|
||||
.find('input')
|
||||
.at(0)
|
||||
.getDOMNode(),
|
||||
);
|
||||
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
|
||||
expect(document.activeElement).toBe(wrapper.find('input').at(0).getDOMNode());
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
['disabled', 'readOnly'].forEach(prop => {
|
||||
it(`should not support allowClear when it is ${prop}`, () => {
|
||||
const wrapper = mount(<Input allowClear defaultValue="111" {...{ [prop]: true }} />);
|
||||
expect(wrapper.find('.ant-input-clear-icon').length).toBe(0);
|
||||
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -183,7 +183,7 @@ describe('TextArea allowClear', () => {
|
||||
const wrappers = [null, undefined, ''].map(val => mount(<TextArea allowClear value={val} />));
|
||||
wrappers.forEach(wrapper => {
|
||||
expect(wrapper.find('textarea').getDOMNode().value).toEqual('');
|
||||
expect(wrapper.find('.ant-input-textarea-clear-icon').exists()).toEqual(false);
|
||||
expect(wrapper.find('.ant-input-textarea-clear-icon-hidden').exists()).toBeTruthy();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@ -236,7 +236,7 @@ describe('TextArea allowClear', () => {
|
||||
|
||||
it('should not support allowClear when it is disabled', () => {
|
||||
const wrapper = mount(<TextArea allowClear defaultValue="111" disabled />);
|
||||
expect(wrapper.find('.ant-input-textarea-clear-icon').length).toBe(0);
|
||||
expect(wrapper.find('.ant-input-textarea-clear-icon-hidden').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('not block input when `value` is undefined', () => {
|
||||
|
@ -19,6 +19,10 @@
|
||||
+ i {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
&-hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================= Input =========================
|
||||
|
@ -8,27 +8,40 @@
|
||||
.@{ant-prefix}-input {
|
||||
.reset-component;
|
||||
.input;
|
||||
}
|
||||
|
||||
//== Style for input-group: input with label, with button or dropdown...
|
||||
.@{ant-prefix}-input-group {
|
||||
.reset-component;
|
||||
.input-group(~'@{ant-prefix}-input');
|
||||
&-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
vertical-align: top; // https://github.com/ant-design/ant-design/issues/6403
|
||||
//== Style for input-group: input with label, with button or dropdown...
|
||||
&-group {
|
||||
.reset-component;
|
||||
.input-group(~'@{ant-prefix}-input');
|
||||
&-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
vertical-align: top; // https://github.com/ant-design/ant-design/issues/6403
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{ant-prefix}-input-password-icon {
|
||||
color: @text-color-secondary;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
&-password-icon {
|
||||
color: @text-color-secondary;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: @input-icon-hover-color;
|
||||
&:hover {
|
||||
color: @input-icon-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
&[type='color'] {
|
||||
height: @input-height-base;
|
||||
|
||||
&.@{ant-prefix}-input-lg {
|
||||
height: @input-height-lg;
|
||||
}
|
||||
&.@{ant-prefix}-input-sm {
|
||||
height: @input-height-sm;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,10 +23,10 @@ A list can be used to display content related to a single subject. The content c
|
||||
| header | List header renderer | string\|ReactNode | - | |
|
||||
| itemLayout | The layout of list, default is `horizontal`, If a vertical list is desired, set the itemLayout property to `vertical` | string | - | |
|
||||
| rowKey | Item's unique key, could be a string or function that returns a string | string\|Function(record):string | `key` | |
|
||||
| loading | Shows a loading indicator while the contents of the list are being fetched | boolean\|[SpinProps](https://ant.design/components/spin/#API) ([more](https://github.com/ant-design/ant-design/issues/8659)) | false | |
|
||||
| loading | Shows a loading indicator while the contents of the list are being fetched | boolean\|[SpinProps](/components/spin/#API) ([more](https://github.com/ant-design/ant-design/issues/8659)) | false | |
|
||||
| loadMore | Shows a load more content | string\|ReactNode | - | |
|
||||
| locale | i18n text including empty text | object | emptyText: 'No Data' <br> | |
|
||||
| pagination | Pagination [config](https://ant.design/components/pagination/), hide it by setting it to false | boolean \| object | false | |
|
||||
| pagination | Pagination [config](/components/pagination/), hide it by setting it to false | boolean \| object | false | |
|
||||
| size | Size of list | `default` \| `large` \| `small` | `default` | |
|
||||
| split | Toggles rendering of the split under the list item | boolean | true | |
|
||||
| dataSource | dataSource array for list | any[] | - | |
|
||||
|
@ -23,7 +23,7 @@ cols: 1
|
||||
| grid | 列表栅格配置 | [object](#List-grid-props) | - | |
|
||||
| header | 列表头部 | string\|ReactNode | - | |
|
||||
| itemLayout | 设置 `List.Item` 布局, 设置成 `vertical` 则竖直样式显示, 默认横排 | string | - | |
|
||||
| loading | 当卡片内容还在加载中时,可以用 `loading` 展示一个占位 | boolean\|[object](https://ant.design/components/spin-cn/#API) ([更多](https://github.com/ant-design/ant-design/issues/8659)) | false | |
|
||||
| loading | 当卡片内容还在加载中时,可以用 `loading` 展示一个占位 | boolean\|[object](/components/spin/#API) ([更多](https://github.com/ant-design/ant-design/issues/8659)) | false | |
|
||||
| loadMore | 加载更多 | string\|ReactNode | - | |
|
||||
| locale | 默认文案设置,目前包括空数据文案 | object | emptyText: '暂无数据' | |
|
||||
| pagination | 对应的 `pagination` 配置, 设置 `false` 不显示 | boolean\|object | false | |
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { ValidateMessages } from 'rc-field-form/lib/interface';
|
||||
import warning from '../_util/warning';
|
||||
|
||||
import { ModalLocale, changeConfirmLocale } from '../modal/locale';
|
||||
@ -30,6 +31,9 @@ export interface Locale {
|
||||
PageHeader?: Object;
|
||||
Icon?: Object;
|
||||
Text?: Object;
|
||||
Form?: {
|
||||
defaultValidateMessages: ValidateMessages;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LocaleProviderProps {
|
||||
|
@ -10,12 +10,22 @@ const localeValues: Locale = {
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Calendar,
|
||||
global: {
|
||||
placeholder: 'אנא בחר',
|
||||
},
|
||||
Table: {
|
||||
filterTitle: 'תפריט סינון',
|
||||
filterConfirm: 'אישור',
|
||||
filterReset: 'איפוס',
|
||||
selectAll: 'בחר הכל',
|
||||
selectInvert: 'הפוך בחירה',
|
||||
selectionAll: 'בחר את כל הנתונים',
|
||||
sortTitle: 'מיון',
|
||||
expand: 'הרחב שורה',
|
||||
collapse: 'צמצם שורהw',
|
||||
triggerDesc: 'לחץ על מיון לפי סדר יורד',
|
||||
triggerAsc: 'לחץ על מיון לפי סדר עולה',
|
||||
cancelSort: 'לחץ כדי לבטל את המיון',
|
||||
},
|
||||
Modal: {
|
||||
okText: 'אישור',
|
||||
@ -41,6 +51,18 @@ const localeValues: Locale = {
|
||||
Empty: {
|
||||
description: 'אין מידע',
|
||||
},
|
||||
Icon: {
|
||||
icon: 'סמל',
|
||||
},
|
||||
Text: {
|
||||
edit: 'ערוך',
|
||||
copy: 'העתק',
|
||||
copied: 'הועתק',
|
||||
expand: 'הרחב',
|
||||
},
|
||||
PageHeader: {
|
||||
back: 'חזרה',
|
||||
},
|
||||
};
|
||||
|
||||
export default localeValues;
|
||||
|
@ -10,6 +10,9 @@ const localeValues: Locale = {
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Calendar,
|
||||
global: {
|
||||
placeholder: 'Пожалуйста выберите',
|
||||
},
|
||||
Table: {
|
||||
filterTitle: 'Фильтр',
|
||||
filterConfirm: 'OK',
|
||||
@ -17,6 +20,11 @@ const localeValues: Locale = {
|
||||
selectAll: 'Выбрать всё',
|
||||
selectInvert: 'Инвертировать выбор',
|
||||
sortTitle: 'Сортировка',
|
||||
expand: 'Развернуть строку',
|
||||
collapse: 'Свернуть строку',
|
||||
triggerDesc: 'Нажмите для сортировки по убыванию',
|
||||
triggerAsc: 'Нажмите для сортировки по возрастанию',
|
||||
cancelSort: 'Нажмите, чтобы отменить сортировку',
|
||||
},
|
||||
Modal: {
|
||||
okText: 'OK',
|
||||
@ -42,6 +50,9 @@ const localeValues: Locale = {
|
||||
Empty: {
|
||||
description: 'Нет данных',
|
||||
},
|
||||
Icon: {
|
||||
icon: 'иконка',
|
||||
},
|
||||
Text: {
|
||||
edit: 'редактировать',
|
||||
copy: 'копировать',
|
||||
|
@ -1,9 +1,12 @@
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import Pagination from 'rc-pagination/lib/locale/zh_CN';
|
||||
import DatePicker from '../date-picker/locale/zh_CN';
|
||||
import TimePicker from '../time-picker/locale/zh_CN';
|
||||
import Calendar from '../calendar/locale/zh_CN';
|
||||
import { Locale } from '../locale-provider';
|
||||
|
||||
const typeTemplate = '${label}不是一个有效的${type}';
|
||||
|
||||
const localeValues: Locale = {
|
||||
locale: 'zh-cn',
|
||||
Pagination,
|
||||
@ -64,6 +67,55 @@ const localeValues: Locale = {
|
||||
PageHeader: {
|
||||
back: '返回',
|
||||
},
|
||||
Form: {
|
||||
defaultValidateMessages: {
|
||||
default: '字段验证错误${label}',
|
||||
required: '请输入${label}',
|
||||
enum: '${label}必须是其中一个[${enum}]',
|
||||
whitespace: '${label}不能为空字符',
|
||||
date: {
|
||||
format: '${label}日期格式无效',
|
||||
parse: '${label}不能转换为日期',
|
||||
invalid: '${label}是一个无效日期',
|
||||
},
|
||||
types: {
|
||||
string: typeTemplate,
|
||||
method: typeTemplate,
|
||||
array: typeTemplate,
|
||||
object: typeTemplate,
|
||||
number: typeTemplate,
|
||||
date: typeTemplate,
|
||||
boolean: typeTemplate,
|
||||
integer: typeTemplate,
|
||||
float: typeTemplate,
|
||||
regexp: typeTemplate,
|
||||
email: typeTemplate,
|
||||
url: typeTemplate,
|
||||
hex: typeTemplate,
|
||||
},
|
||||
string: {
|
||||
len: '${label}须为${len}个字符',
|
||||
min: '${label}最少${min}个字符',
|
||||
max: '${label}最多${max}个字符',
|
||||
range: '${label}须在${min}-${max}字符之间',
|
||||
},
|
||||
number: {
|
||||
len: '${label}必须等于${len}',
|
||||
min: '${label}最小值为${min}',
|
||||
max: '${label}最大值为${max}',
|
||||
range: '${label}须在${min}-${max}之间',
|
||||
},
|
||||
array: {
|
||||
len: '须为${len}个${label}',
|
||||
min: '最少${min}个${label}',
|
||||
max: '最多${max}个${label}',
|
||||
range: '${label}数量须在${min}-${max}之间',
|
||||
},
|
||||
pattern: {
|
||||
mismatch: '${label}与模式不匹配${pattern}',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default localeValues;
|
||||
|
@ -126,30 +126,26 @@ exports[`renders ./components/menu/demo/horizontal.md correctly 1`] = `
|
||||
role="button"
|
||||
>
|
||||
<span
|
||||
class="submenu-title-wrapper"
|
||||
aria-label="setting"
|
||||
class="anticon anticon-setting"
|
||||
role="img"
|
||||
>
|
||||
<span
|
||||
aria-label="setting"
|
||||
class="anticon anticon-setting"
|
||||
role="img"
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="setting"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="setting"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
Navigation Three - Submenu
|
||||
<path
|
||||
d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
Navigation Three - Submenu
|
||||
<i
|
||||
class="ant-menu-submenu-arrow"
|
||||
/>
|
||||
|
@ -15,11 +15,7 @@ Horizontal top navigation menu.
|
||||
|
||||
```jsx
|
||||
import { Menu } from 'antd';
|
||||
import {
|
||||
MailOutlined,
|
||||
AppstoreOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { MailOutlined, AppstoreOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
@ -48,10 +44,10 @@ class App extends React.Component {
|
||||
</Menu.Item>
|
||||
<SubMenu
|
||||
title={
|
||||
<span className="submenu-title-wrapper">
|
||||
<>
|
||||
<SettingOutlined />
|
||||
Navigation Three - Submenu
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Menu.ItemGroup title="Item 1">
|
||||
|
@ -210,10 +210,6 @@
|
||||
z-index: @zindex-dropdown;
|
||||
border-radius: @border-radius-base;
|
||||
|
||||
.submenu-title-wrapper {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
|
@ -22,6 +22,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-dark {
|
||||
.@{menu-prefix-cls}-inline,
|
||||
.@{menu-prefix-cls}-vertical {
|
||||
.@{menu-prefix-cls}-rtl& {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-vertical&-sub,
|
||||
&-vertical-left&-sub,
|
||||
&-vertical-right&-sub {
|
||||
@ -56,15 +65,6 @@
|
||||
}
|
||||
|
||||
&-submenu {
|
||||
&-popup {
|
||||
.submenu-title-wrapper {
|
||||
.@{menu-prefix-cls}-submenu-rtl& {
|
||||
padding-right: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-vertical,
|
||||
&-vertical-left,
|
||||
&-vertical-right,
|
||||
@ -134,6 +134,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-inline-collapsed&-vertical {
|
||||
.@{menu-prefix-cls}-submenu-title {
|
||||
.@{menu-prefix-cls}-rtl& {
|
||||
padding: 0 (@menu-collapsed-width - @menu-icon-size-lg) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-item-group-list {
|
||||
.@{menu-prefix-cls}-item,
|
||||
.@{menu-prefix-cls}-submenu-title {
|
||||
|
@ -35,55 +35,59 @@ export default class ActionButton extends React.Component<ActionButtonProps, Act
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
|
||||
handlePromiseOnOk(returnValueOfOnOk?: PromiseLike<any>) {
|
||||
const { closeModal } = this.props;
|
||||
if (!returnValueOfOnOk || !returnValueOfOnOk.then) {
|
||||
return;
|
||||
}
|
||||
this.setState({ loading: true });
|
||||
returnValueOfOnOk.then(
|
||||
(...args: any[]) => {
|
||||
// It's unnecessary to set loading=false, for the Modal will be unmounted after close.
|
||||
// this.setState({ loading: false });
|
||||
closeModal(...args);
|
||||
},
|
||||
(e: Error) => {
|
||||
// Emit error when catch promise reject
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
// See: https://github.com/ant-design/ant-design/issues/6183
|
||||
this.setState({ loading: false });
|
||||
this.clicked = false;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
const { actionFn, closeModal } = this.props;
|
||||
if (this.clicked) {
|
||||
return;
|
||||
}
|
||||
this.clicked = true;
|
||||
if (actionFn) {
|
||||
let ret;
|
||||
if (actionFn.length) {
|
||||
ret = actionFn(closeModal);
|
||||
} else {
|
||||
ret = actionFn();
|
||||
if (!ret) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
if (ret && ret.then) {
|
||||
this.setState({ loading: true });
|
||||
ret.then(
|
||||
(...args: any[]) => {
|
||||
// It's unnecessary to set loading=false, for the Modal will be unmounted after close.
|
||||
// this.setState({ loading: false });
|
||||
closeModal(...args);
|
||||
},
|
||||
(e: Error) => {
|
||||
// Emit error when catch promise reject
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
// See: https://github.com/ant-design/ant-design/issues/6183
|
||||
this.setState({ loading: false });
|
||||
this.clicked = false;
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!actionFn) {
|
||||
closeModal();
|
||||
return;
|
||||
}
|
||||
let returnValueOfOnOk;
|
||||
if (actionFn.length) {
|
||||
returnValueOfOnOk = actionFn(closeModal);
|
||||
// https://github.com/ant-design/ant-design/issues/23358
|
||||
this.clicked = false;
|
||||
} else {
|
||||
returnValueOfOnOk = actionFn();
|
||||
if (!returnValueOfOnOk) {
|
||||
closeModal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.handlePromiseOnOk(returnValueOfOnOk);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { type, children, buttonProps } = this.props;
|
||||
const { loading } = this.state;
|
||||
return (
|
||||
<Button
|
||||
type={type}
|
||||
onClick={this.onClick}
|
||||
loading={loading}
|
||||
{...buttonProps}
|
||||
>
|
||||
<Button type={type} onClick={this.onClick} loading={loading} {...buttonProps}>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
|
@ -9,6 +9,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
afterEach(() => {
|
||||
errorSpy.mockReset();
|
||||
document.body.innerHTML = '';
|
||||
Modal.destroyAll();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@ -74,12 +75,19 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not hide confirm when onOk return Promise.resolve', () => {
|
||||
open({
|
||||
onOk: () => Promise.resolve(''),
|
||||
});
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
expect($$('.ant-modal-confirm')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should emit error when onOk return Promise.reject', () => {
|
||||
const error = new Error('something wrong');
|
||||
open({
|
||||
onOk: () => Promise.reject(error),
|
||||
});
|
||||
// Fifth Modal
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
// wait promise
|
||||
return Promise.resolve().then(() => {
|
||||
@ -125,6 +133,22 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should not close modals when click confirm button when onOk has argument', () => {
|
||||
jest.useFakeTimers();
|
||||
['info', 'success', 'warning', 'error'].forEach(type => {
|
||||
Modal[type]({
|
||||
title: 'title',
|
||||
content: 'content',
|
||||
onOk: close => null, // eslint-disable-line no-unused-vars
|
||||
});
|
||||
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
|
||||
$$('.ant-btn')[0].click();
|
||||
jest.runAllTimers();
|
||||
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
|
||||
});
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('could be update', () => {
|
||||
jest.useFakeTimers();
|
||||
['info', 'success', 'warning', 'error'].forEach(type => {
|
||||
@ -235,9 +259,23 @@ describe('Modal.confirm triggers callbacks correctly', () => {
|
||||
it('ok button should trigger onOk once when click it many times quickly', () => {
|
||||
const onOk = jest.fn();
|
||||
open({ onOk });
|
||||
// Fifth Modal
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
expect(onOk).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/23358
|
||||
it('ok button should trigger onOk multiple times when onOk has close argument', () => {
|
||||
const onOk = jest.fn();
|
||||
open({
|
||||
onOk: close => {
|
||||
onOk();
|
||||
(() => {})(close); // do nothing
|
||||
},
|
||||
});
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
$$('.ant-btn-primary')[0].click();
|
||||
expect(onOk).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
@ -75,8 +75,8 @@ The items listed above are all functions, expecting a settings object as paramet
|
||||
| title | Title | string\|ReactNode | - |
|
||||
| width | Width of the modal dialog | string\|number | 416 |
|
||||
| zIndex | The `z-index` of the Modal | Number | 1000 |
|
||||
| onCancel | Specify a function that will be called when the user clicks the Cancel button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function | - |
|
||||
| onOk | Specify a function that will be called when the user clicks the OK button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function | - |
|
||||
| onCancel | Specify a function that will be called when the user clicks the Cancel button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - |
|
||||
| onOk | Specify a function that will be called when the user clicks the OK button. The parameter of this function is a function whose execution should include closing the dialog. You can also just return a promise and when the promise is resolved, the modal dialog will also be closed | function(close) | - |
|
||||
|
||||
All the `Modal.method`s will return a reference, and then we can update and close the modal dialog by the reference.
|
||||
|
||||
|
@ -77,8 +77,8 @@ title: Modal
|
||||
| title | 标题 | string\|ReactNode | - |
|
||||
| width | 宽度 | string\|number | 416 |
|
||||
| zIndex | 设置 Modal 的 `z-index` | Number | 1000 |
|
||||
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | - |
|
||||
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function | - |
|
||||
| onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - |
|
||||
| onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | function(close) | - |
|
||||
|
||||
以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。
|
||||
|
||||
|
@ -4,11 +4,26 @@ import PageHeader from '..';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { spyElementPrototypes } from '../../__tests__/util/domHook';
|
||||
|
||||
describe('PageHeader', () => {
|
||||
mountTest(PageHeader);
|
||||
rtlTest(PageHeader);
|
||||
|
||||
let spy;
|
||||
|
||||
beforeAll(() => {
|
||||
spy = spyElementPrototypes(HTMLElement, {
|
||||
getBoundingClientRect: () => ({
|
||||
width: 100,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('pageHeader should not contain back it back', () => {
|
||||
const routes = [
|
||||
{
|
||||
@ -101,4 +116,11 @@ describe('PageHeader', () => {
|
||||
|
||||
expect(render(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('change container width', () => {
|
||||
const wrapper = mount(<PageHeader title="Page Title" extra="extra" />);
|
||||
wrapper.triggerResize();
|
||||
wrapper.update();
|
||||
expect(wrapper.find('.ant-page-header').hasClass('ant-page-header-compact')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -21,9 +21,9 @@ PageHeader can be used to highlight the page topic, display important informatio
|
||||
| ghost | PageHeader type, will change background color | boolean | true | |
|
||||
| avatar | Avatar next to the title bar | [avatar props](/components/avatar/) | - | |
|
||||
| backIcon | Custom back icon, if false the back icon will not be displayed | ReactNode \| boolean | `<ArrowLeft />` | |
|
||||
| tags | Tag list next to title | [Tag](https://ant.design/components/tag-cn/)[] \| [Tag](https://ant.design/components/tag-cn/) | - | |
|
||||
| tags | Tag list next to title | [Tag](/components/tag/)[] \| [Tag](/components/tag/) | - | |
|
||||
| extra | Operating area, at the end of the line of the title line | ReactNode | - | |
|
||||
| breadcrumb | Breadcrumb configuration | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - | |
|
||||
| breadcrumb | Breadcrumb configuration | [breadcrumb](/components/breadcrumb/) | - | |
|
||||
| footer | PageHeader's footer, generally used to render TabBar | ReactNode | - | |
|
||||
| onBack | Back icon click event | `()=>void` | `()=>history.back()` | |
|
||||
|
||||
|
@ -116,7 +116,7 @@ const renderChildren = (prefixCls: string, children: React.ReactNode) => {
|
||||
const PageHeader: React.FC<PageHeaderProps> = props => {
|
||||
const [compact, updateCompact] = React.useState(false);
|
||||
const onResize = ({ width }: { width: number }) => {
|
||||
updateCompact(width < 540);
|
||||
updateCompact(width < 768);
|
||||
};
|
||||
return (
|
||||
<ConfigConsumer>
|
||||
|
@ -21,9 +21,9 @@ subtitle: 页头
|
||||
| ghost | pageHeader 的类型,将会改变背景颜色 | boolean | true | |
|
||||
| avatar | 标题栏旁的头像 | [avatar props](/components/avatar/) | - | |
|
||||
| backIcon | 自定义 back icon ,如果为 false 不渲染 back icon | ReactNode \| boolean | `<ArrowLeft />` | |
|
||||
| tags | title 旁的 tag 列表 | [Tag](https://ant.design/components/tag-cn/)[] \| [Tag](https://ant.design/components/tag-cn/) | - | |
|
||||
| tags | title 旁的 tag 列表 | [Tag](/components/tag/)[] \| [Tag](/components/tag/) | - | |
|
||||
| extra | 操作区,位于 title 行的行尾 | ReactNode | - | |
|
||||
| breadcrumb | 面包屑的配置 | [breadcrumb](https://ant.design/components/breadcrumb-cn/) | - | |
|
||||
| breadcrumb | 面包屑的配置 | [breadcrumb](/components/breadcrumb/) | - | |
|
||||
| footer | PageHeader 的页脚,一般用于渲染 TabBar | ReactNode | - | |
|
||||
| onBack | 返回按钮的点击事件 | `()=>void` | `()=>history.back()` | |
|
||||
|
||||
|
@ -56,6 +56,7 @@
|
||||
&-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: (@margin-xs / 2) 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -82,7 +83,9 @@
|
||||
}
|
||||
|
||||
&-extra {
|
||||
margin: (@margin-xs / 2) 0;
|
||||
white-space: nowrap;
|
||||
|
||||
> * {
|
||||
margin-left: @margin-sm;
|
||||
white-space: unset;
|
||||
@ -112,11 +115,7 @@
|
||||
}
|
||||
|
||||
&-compact &-heading {
|
||||
flex-direction: column;
|
||||
|
||||
&-extra {
|
||||
margin-top: @margin-xs;
|
||||
}
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ The difference with the `confirm` modal dialog is that it's more lightweight tha
|
||||
| icon | customize icon of confirmation | ReactNode | `<ExclamationCircle />` |
|
||||
| disabled | is show popconfirm when click its childrenNode | boolean | false |
|
||||
|
||||
Consult [Tooltip's documentation](https://ant.design/components/tooltip/#API) to find more APIs.
|
||||
Consult [Tooltip's documentation](/components/tooltip/#API) to find more APIs.
|
||||
|
||||
## Note
|
||||
|
||||
|
@ -19,7 +19,7 @@ Comparing with `Tooltip`, besides information `Popover` card can also provide ac
|
||||
| content | Content of the card | string\|ReactNode\|() => ReactNode | - | |
|
||||
| title | Title of the card | string\|ReactNode\|() => ReactNode | - | |
|
||||
|
||||
Consult [Tooltip's documentation](https://ant.design/components/tooltip/#API) to find more APIs.
|
||||
Consult [Tooltip's documentation](/components/tooltip/#API) to find more APIs.
|
||||
|
||||
## Note
|
||||
|
||||
|
@ -34,9 +34,9 @@ Select component to select value from options.
|
||||
| dropdownRender | Customize dropdown content | (menuNode: ReactNode, props) => ReactNode | - | |
|
||||
| dropdownStyle | style of dropdown menu | object | - | |
|
||||
| filterOption | If true, filter options by input, if function, filter options against it. The function will receive two arguments, `inputValue` and `option`, if the function returns `true`, the option will be included in the filtered set; Otherwise, it will be excluded. | boolean or function(inputValue, option) | true | |
|
||||
| firstActiveValue | Value of action option by default | string\|string\[] | - | |
|
||||
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. [Example](https://codesandbox.io/s/4j168r7jw0) | function(triggerNode) | () => document.body | |
|
||||
| labelInValue | whether to embed label in value, turn the format of value from `string` to `{key: string, label: ReactNode}` | boolean | false | |
|
||||
| listHeight | Config popup height | number | 256 | |
|
||||
| maxTagCount | Max tag count to show | number | - | |
|
||||
| maxTagTextLength | Max tag text length to show | number | - | |
|
||||
| maxTagPlaceholder | Placeholder for not showing tags | ReactNode/function(omittedValues) | - | |
|
||||
|
@ -35,9 +35,9 @@ title: Select
|
||||
| dropdownRender | 自定义下拉框内容 | (menuNode: ReactNode, props) => ReactNode | - | |
|
||||
| dropdownStyle | 下拉菜单的 style 属性 | object | - | |
|
||||
| filterOption | 是否根据输入项进行筛选。当其为一个函数时,会接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 `true`,反之则返回 `false`。 | boolean or function(inputValue, option) | true | |
|
||||
| firstActiveValue | 默认高亮的选项 | string\|string\[] | - | |
|
||||
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。[示例](https://codesandbox.io/s/4j168r7jw0) | Function(triggerNode) | () => document.body | |
|
||||
| labelInValue | 是否把每个选项的 label 包装到 value 中,会把 Select 的 value 类型从 `string` 变为 `{key: string, label: ReactNode}` 的格式 | boolean | false | |
|
||||
| listHeight | 设置弹窗滚动高度 | number | 256 | |
|
||||
| maxTagCount | 最多显示多少个 tag | number | - | |
|
||||
| maxTagTextLength | 最大显示的 tag 文本长度 | number | - | |
|
||||
| maxTagPlaceholder | 隐藏 tag 时显示的内容 | ReactNode/function(omittedValues) | - | |
|
||||
|
@ -98,7 +98,6 @@
|
||||
// ========================== Arrow ==========================
|
||||
&-arrow {
|
||||
.iconfont-mixin();
|
||||
|
||||
position: absolute;
|
||||
top: 53%;
|
||||
right: @control-padding-horizontal - 1px;
|
||||
@ -109,8 +108,6 @@
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
// transform-origin: 50% 50%;
|
||||
pointer-events: none;
|
||||
|
||||
.anticon {
|
||||
vertical-align: top;
|
||||
@ -124,6 +121,10 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&.anticon-down {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// ========================== Clear ==========================
|
||||
|
@ -13,170 +13,178 @@
|
||||
* since chrome may update to redesign with its align logic.
|
||||
*/
|
||||
|
||||
.@{select-prefix-cls}-multiple {
|
||||
// ========================= Selector =========================
|
||||
.@{select-prefix-cls}-selector {
|
||||
.select-selector();
|
||||
.select-search-input-without-border();
|
||||
.@{select-prefix-cls} {
|
||||
&-multiple {
|
||||
// ========================= Selector =========================
|
||||
.@{select-prefix-cls}-selector {
|
||||
.select-selector();
|
||||
.select-search-input-without-border();
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
// Multiple is little different that horizontal is follow the vertical
|
||||
padding: @select-multiple-padding @input-padding-vertical-base;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
// Multiple is little different that horizontal is follow the vertical
|
||||
padding: @select-multiple-padding @input-padding-vertical-base;
|
||||
|
||||
.@{select-prefix-cls}-show-search& {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
margin: @select-multiple-item-spacing-half 0;
|
||||
line-height: @select-multiple-item-height;
|
||||
content: '\a0';
|
||||
}
|
||||
}
|
||||
|
||||
&.@{select-prefix-cls}-allow-clear .@{select-prefix-cls}-selector {
|
||||
padding-right: @font-size-sm + @control-padding-horizontal;
|
||||
}
|
||||
|
||||
// ======================== Selections ========================
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: none;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
|
||||
height: @select-multiple-item-height;
|
||||
margin-top: @select-multiple-item-spacing-half;
|
||||
margin-right: @input-padding-vertical-base;
|
||||
margin-bottom: @select-multiple-item-spacing-half;
|
||||
padding: 0 (@padding-xs / 2) 0 @padding-xs;
|
||||
line-height: @select-multiple-item-height - @select-multiple-item-border-width * 2;
|
||||
background: @select-selection-item-bg;
|
||||
border: 1px solid @select-selection-item-border-color;
|
||||
border-radius: @border-radius-base;
|
||||
cursor: default;
|
||||
transition: font-size 0.3s, line-height 0.3s, height 0.3s;
|
||||
user-select: none;
|
||||
|
||||
// It's ok not to do this, but 24px makes bottom narrow in view should adjust
|
||||
&-content {
|
||||
display: inline-block;
|
||||
margin-right: @padding-xs / 2;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-remove {
|
||||
.iconfont-mixin();
|
||||
|
||||
display: inline-block;
|
||||
color: @text-color-secondary;
|
||||
font-weight: bold;
|
||||
font-size: @font-size-sm;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
.iconfont-size-under-12px(10px);
|
||||
&:hover {
|
||||
color: @icon-color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================== Input ==========================
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
position: relative;
|
||||
margin-left: @select-multiple-padding / 2;
|
||||
|
||||
&-input,
|
||||
&-mirror {
|
||||
font-family: @font-family;
|
||||
line-height: @line-height-base;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
&-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-mirror {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
white-space: nowrap;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/22906
|
||||
&:first-child .@{select-prefix-cls}-selection-search-input {
|
||||
margin-left: 6.5px;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================= Placeholder =======================
|
||||
.@{select-prefix-cls}-selection-placeholder {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: @input-padding-horizontal;
|
||||
left: @input-padding-horizontal;
|
||||
transform: translateY(-50%);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// == Size ==
|
||||
// ============================================================
|
||||
.select-size(@suffix, @input-height) {
|
||||
@merged-cls: ~'@{select-prefix-cls}-@{suffix}';
|
||||
&.@{merged-cls} {
|
||||
@select-selection-height: @input-height - @input-padding-vertical-base * 2;
|
||||
@select-height-without-border: @input-height - @border-width-base * 2;
|
||||
|
||||
.@{select-prefix-cls}-selector::after {
|
||||
line-height: @select-selection-height;
|
||||
.@{select-prefix-cls}-show-search& {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
height: @select-selection-height;
|
||||
line-height: @select-selection-height - @border-width-base * 2;
|
||||
&::after {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
margin: @select-multiple-item-spacing-half 0;
|
||||
line-height: @select-multiple-item-height;
|
||||
content: '\a0';
|
||||
}
|
||||
}
|
||||
|
||||
&.@{select-prefix-cls}-allow-clear .@{select-prefix-cls}-selector {
|
||||
padding-right: @font-size-sm + @control-padding-horizontal;
|
||||
}
|
||||
|
||||
// ======================== Selections ========================
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: none;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
|
||||
height: @select-multiple-item-height;
|
||||
margin-top: @select-multiple-item-spacing-half;
|
||||
margin-right: @input-padding-vertical-base;
|
||||
margin-bottom: @select-multiple-item-spacing-half;
|
||||
padding: 0 (@padding-xs / 2) 0 @padding-xs;
|
||||
line-height: @select-multiple-item-height - @select-multiple-item-border-width * 2;
|
||||
background: @select-selection-item-bg;
|
||||
border: 1px solid @select-selection-item-border-color;
|
||||
border-radius: @border-radius-base;
|
||||
cursor: default;
|
||||
transition: font-size 0.3s, line-height 0.3s, height 0.3s;
|
||||
user-select: none;
|
||||
|
||||
// It's ok not to do this, but 24px makes bottom narrow in view should adjust
|
||||
&-content {
|
||||
display: inline-block;
|
||||
margin-right: @padding-xs / 2;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
height: @select-selection-height + @select-multiple-padding;
|
||||
line-height: @select-selection-height + @select-multiple-padding;
|
||||
&-remove {
|
||||
.iconfont-mixin();
|
||||
|
||||
&-input,
|
||||
&-mirror {
|
||||
height: @select-selection-height;
|
||||
line-height: @select-selection-height - @border-width-base * 2;
|
||||
display: inline-block;
|
||||
color: @text-color-secondary;
|
||||
font-weight: bold;
|
||||
font-size: @font-size-sm;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
.iconfont-size-under-12px(10px);
|
||||
&:hover {
|
||||
color: @icon-color-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-size('lg', @input-height-lg);
|
||||
.select-size('sm', @input-height-sm);
|
||||
// ========================== Input ==========================
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
position: relative;
|
||||
margin-left: @select-multiple-padding / 2;
|
||||
|
||||
// Size small need additional set padding
|
||||
&.@{select-prefix-cls}-sm {
|
||||
&-input,
|
||||
&-mirror {
|
||||
font-family: @font-family;
|
||||
line-height: @line-height-base;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
&-input {
|
||||
width: 100%;
|
||||
min-width: 3px;
|
||||
}
|
||||
|
||||
&-mirror {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
white-space: nowrap;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/22906
|
||||
&:first-child .@{select-prefix-cls}-selection-search-input {
|
||||
margin-left: 6.5px;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================= Placeholder =======================
|
||||
.@{select-prefix-cls}-selection-placeholder {
|
||||
left: @input-padding-horizontal-sm;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: @input-padding-horizontal;
|
||||
left: @input-padding-horizontal;
|
||||
transform: translateY(-50%);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
// https://github.com/ant-design/ant-design/issues/22906
|
||||
.@{select-prefix-cls}-selection-search:first-child .@{select-prefix-cls}-selection-search-input {
|
||||
margin-left: 3px;
|
||||
|
||||
// ============================================================
|
||||
// == Size ==
|
||||
// ============================================================
|
||||
.select-size(@suffix, @input-height) {
|
||||
@merged-cls: ~'@{select-prefix-cls}-@{suffix}';
|
||||
&.@{merged-cls} {
|
||||
@select-selection-height: @input-height - @input-padding-vertical-base * 2;
|
||||
@select-height-without-border: @input-height - @border-width-base * 2;
|
||||
|
||||
.@{select-prefix-cls}-selector::after {
|
||||
line-height: @select-selection-height;
|
||||
}
|
||||
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
height: @select-selection-height;
|
||||
line-height: @select-selection-height - @border-width-base * 2;
|
||||
}
|
||||
|
||||
.@{select-prefix-cls}-selection-search {
|
||||
height: @select-selection-height + @select-multiple-padding;
|
||||
line-height: @select-selection-height + @select-multiple-padding;
|
||||
|
||||
&-input,
|
||||
&-mirror {
|
||||
height: @select-selection-height;
|
||||
line-height: @select-selection-height - @border-width-base * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-size('lg', @input-height-lg);
|
||||
.select-size('sm', @input-height-sm);
|
||||
|
||||
// Size small need additional set padding
|
||||
&.@{select-prefix-cls}-sm {
|
||||
.@{select-prefix-cls}-selection-placeholder {
|
||||
left: @input-padding-horizontal-sm;
|
||||
}
|
||||
// https://github.com/ant-design/ant-design/issues/22906
|
||||
.@{select-prefix-cls}-selection-search:first-child
|
||||
.@{select-prefix-cls}-selection-search-input {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
&.@{select-prefix-cls}-lg {
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
height: @select-multiple-item-height-lg;
|
||||
line-height: @select-multiple-item-height-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.@{select-prefix-cls}-lg {
|
||||
.@{select-prefix-cls}-selection-item {
|
||||
height: @select-multiple-item-height-lg;
|
||||
line-height: @select-multiple-item-height-lg;
|
||||
}
|
||||
|
||||
&-disabled .@{select-prefix-cls}-selection-item-remove {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,7 @@
|
||||
// ========================== Popup ==========================
|
||||
&-dropdown {
|
||||
&-rtl {
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@
|
||||
|
||||
.@{select-prefix-cls}-selection-item,
|
||||
.@{select-prefix-cls}-selection-placeholder {
|
||||
line-height: @input-height;
|
||||
line-height: @input-height - 2 * @border-width-base;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,17 +8,11 @@ export interface AvatarProps extends Omit<SkeletonElementProps, 'shape'> {
|
||||
shape?: 'circle' | 'square';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class SkeletonAvatar extends React.Component<AvatarProps, any> {
|
||||
static defaultProps: Partial<AvatarProps> = {
|
||||
size: 'default',
|
||||
shape: 'circle',
|
||||
};
|
||||
|
||||
renderSkeletonAvatar = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, className, active } = this.props;
|
||||
const SkeletonAvatar = (props: AvatarProps) => {
|
||||
const renderSkeletonAvatar = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, className, active } = props;
|
||||
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
|
||||
const otherProps = omit(this.props, ['prefixCls']);
|
||||
const otherProps = omit(props, ['prefixCls']);
|
||||
const cls = classNames(prefixCls, className, `${prefixCls}-element`, {
|
||||
[`${prefixCls}-active`]: active,
|
||||
});
|
||||
@ -28,10 +22,12 @@ class SkeletonAvatar extends React.Component<AvatarProps, any> {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderSkeletonAvatar}</ConfigConsumer>;
|
||||
}
|
||||
return <ConfigConsumer>{renderSkeletonAvatar}</ConfigConsumer>;
|
||||
}
|
||||
|
||||
SkeletonAvatar.defaultProps = {
|
||||
size: 'default',
|
||||
shape: 'circle',
|
||||
};
|
||||
|
||||
export default SkeletonAvatar;
|
||||
|
@ -4,20 +4,15 @@ import classNames from 'classnames';
|
||||
import Element, { SkeletonElementProps } from './Element';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
|
||||
interface SkeletonButtonProps extends Omit<SkeletonElementProps, 'size'> {
|
||||
export interface SkeletonButtonProps extends Omit<SkeletonElementProps, 'size'> {
|
||||
size?: 'large' | 'small' | 'default';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class SkeletonButton extends React.Component<SkeletonButtonProps, any> {
|
||||
static defaultProps: Partial<SkeletonButtonProps> = {
|
||||
size: 'default',
|
||||
};
|
||||
|
||||
renderSkeletonButton = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, className, active } = this.props;
|
||||
const SkeletonButton = (props: SkeletonButtonProps) => {
|
||||
const renderSkeletonButton = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, className, active } = props;
|
||||
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
|
||||
const otherProps = omit(this.props, ['prefixCls']);
|
||||
const otherProps = omit(props, ['prefixCls']);
|
||||
const cls = classNames(prefixCls, className, `${prefixCls}-element`, {
|
||||
[`${prefixCls}-active`]: active,
|
||||
});
|
||||
@ -27,10 +22,11 @@ class SkeletonButton extends React.Component<SkeletonButtonProps, any> {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <ConfigConsumer>{renderSkeletonButton}</ConfigConsumer>;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderSkeletonButton}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
SkeletonButton.defaultProps = {
|
||||
size: 'default',
|
||||
};
|
||||
|
||||
export default SkeletonButton;
|
||||
|
@ -10,37 +10,35 @@ export interface SkeletonElementProps {
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class Element extends React.Component<SkeletonElementProps, any> {
|
||||
render() {
|
||||
const { prefixCls, className, style, size, shape } = this.props;
|
||||
const Element = (props: SkeletonElementProps) => {
|
||||
const { prefixCls, className, style, size, shape } = props;
|
||||
|
||||
const sizeCls = classNames({
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
});
|
||||
const sizeCls = classNames({
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
});
|
||||
|
||||
const shapeCls = classNames({
|
||||
[`${prefixCls}-circle`]: shape === 'circle',
|
||||
[`${prefixCls}-square`]: shape === 'square',
|
||||
[`${prefixCls}-round`]: shape === 'round',
|
||||
});
|
||||
const shapeCls = classNames({
|
||||
[`${prefixCls}-circle`]: shape === 'circle',
|
||||
[`${prefixCls}-square`]: shape === 'square',
|
||||
[`${prefixCls}-round`]: shape === 'round',
|
||||
});
|
||||
|
||||
const sizeStyle: React.CSSProperties =
|
||||
typeof size === 'number'
|
||||
? {
|
||||
width: size,
|
||||
height: size,
|
||||
lineHeight: `${size}px`,
|
||||
}
|
||||
: {};
|
||||
return (
|
||||
<span
|
||||
className={classNames(prefixCls, className, sizeCls, shapeCls)}
|
||||
style={{ ...sizeStyle, ...style }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const sizeStyle: React.CSSProperties =
|
||||
typeof size === 'number'
|
||||
? {
|
||||
width: size,
|
||||
height: size,
|
||||
lineHeight: `${size}px`,
|
||||
}
|
||||
: {};
|
||||
return (
|
||||
<span
|
||||
className={classNames(prefixCls, className, sizeCls, shapeCls)}
|
||||
style={{ ...sizeStyle, ...style }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Element;
|
||||
|
@ -4,20 +4,15 @@ import classNames from 'classnames';
|
||||
import Element, { SkeletonElementProps } from './Element';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
|
||||
interface SkeletonInputProps extends Omit<SkeletonElementProps, 'size' | 'shape'> {
|
||||
export interface SkeletonInputProps extends Omit<SkeletonElementProps, 'size' | 'shape'> {
|
||||
size?: 'large' | 'small' | 'default';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
class SkeletonInput extends React.Component<SkeletonInputProps, any> {
|
||||
static defaultProps: Partial<SkeletonInputProps> = {
|
||||
size: 'default',
|
||||
};
|
||||
|
||||
renderSkeletonInput = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, className, active } = this.props;
|
||||
const SkeletonInput = (props: SkeletonInputProps) => {
|
||||
const renderSkeletonInput = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, className, active } = props;
|
||||
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
|
||||
const otherProps = omit(this.props, ['prefixCls']);
|
||||
const otherProps = omit(props, ['prefixCls']);
|
||||
const cls = classNames(prefixCls, className, `${prefixCls}-element`, {
|
||||
[`${prefixCls}-active`]: active,
|
||||
});
|
||||
@ -27,10 +22,11 @@ class SkeletonInput extends React.Component<SkeletonInputProps, any> {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <ConfigConsumer>{renderSkeletonInput}</ConfigConsumer>;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderSkeletonInput}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
SkeletonInput.defaultProps = {
|
||||
size: 'default',
|
||||
};
|
||||
|
||||
export default SkeletonInput;
|
||||
|
@ -11,9 +11,9 @@ export interface SkeletonParagraphProps {
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
class Paragraph extends React.Component<SkeletonParagraphProps, {}> {
|
||||
getWidth(index: number) {
|
||||
const { width, rows = 2 } = this.props;
|
||||
const Paragraph = (props: SkeletonParagraphProps) => {
|
||||
const getWidth = (index: number) => {
|
||||
const { width, rows = 2 } = props;
|
||||
if (Array.isArray(width)) {
|
||||
return width[index];
|
||||
}
|
||||
@ -22,20 +22,17 @@ class Paragraph extends React.Component<SkeletonParagraphProps, {}> {
|
||||
return width;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { prefixCls, className, style, rows } = this.props;
|
||||
const rowList = [...Array(rows)].map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index} style={{ width: this.getWidth(index) }} />
|
||||
));
|
||||
return (
|
||||
<ul className={classNames(prefixCls, className)} style={style}>
|
||||
{rowList}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
const { prefixCls, className, style, rows } = props;
|
||||
const rowList = [...Array(rows)].map((_, index) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<li key={index} style={{ width: getWidth(index) }}/>
|
||||
));
|
||||
return (
|
||||
<ul className={classNames(prefixCls, className)} style={style}>
|
||||
{rowList}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default Paragraph;
|
||||
|
@ -3,13 +3,14 @@ import classNames from 'classnames';
|
||||
import Title, { SkeletonTitleProps } from './Title';
|
||||
import Paragraph, { SkeletonParagraphProps } from './Paragraph';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import SkeletonButton from './Button';
|
||||
import Element from './Element';
|
||||
import SkeletonAvatar, { AvatarProps } from './Avatar';
|
||||
import SkeletonButton from './Button';
|
||||
import SkeletonInput from './Input';
|
||||
|
||||
/* This only for skeleton internal. */
|
||||
interface SkeletonAvatarProps extends Omit<AvatarProps, 'active'> {}
|
||||
interface SkeletonAvatarProps extends Omit<AvatarProps, 'active'> {
|
||||
}
|
||||
|
||||
export interface SkeletonProps {
|
||||
active?: boolean;
|
||||
@ -68,20 +69,8 @@ function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): Skeleton
|
||||
return basicProps;
|
||||
}
|
||||
|
||||
class Skeleton extends React.Component<SkeletonProps, any> {
|
||||
static Button: typeof SkeletonButton;
|
||||
|
||||
static Avatar: typeof SkeletonAvatar;
|
||||
|
||||
static Input: typeof SkeletonInput;
|
||||
|
||||
static defaultProps: Partial<SkeletonProps> = {
|
||||
avatar: false,
|
||||
title: true,
|
||||
paragraph: true,
|
||||
};
|
||||
|
||||
renderSkeleton = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const Skeleton = (props: SkeletonProps) => {
|
||||
const renderSkeleton = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
loading,
|
||||
@ -91,11 +80,11 @@ class Skeleton extends React.Component<SkeletonProps, any> {
|
||||
title,
|
||||
paragraph,
|
||||
active,
|
||||
} = this.props;
|
||||
} = props;
|
||||
|
||||
const prefixCls = getPrefixCls('skeleton', customizePrefixCls);
|
||||
|
||||
if (loading || !('loading' in this.props)) {
|
||||
if (loading || !('loading' in props)) {
|
||||
const hasAvatar = !!avatar;
|
||||
const hasTitle = !!title;
|
||||
const hasParagraph = !!paragraph;
|
||||
@ -166,10 +155,17 @@ class Skeleton extends React.Component<SkeletonProps, any> {
|
||||
|
||||
return children;
|
||||
};
|
||||
return <ConfigConsumer>{renderSkeleton}</ConfigConsumer>;
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderSkeleton}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
Skeleton.defaultProps = {
|
||||
avatar: false,
|
||||
title: true,
|
||||
paragraph: true,
|
||||
};
|
||||
|
||||
Skeleton.Button = SkeletonButton;
|
||||
Skeleton.Avatar = SkeletonAvatar;
|
||||
Skeleton.Input = SkeletonInput;
|
||||
|
||||
export default Skeleton;
|
||||
|
@ -1,11 +1,5 @@
|
||||
import Skeleton from './Skeleton';
|
||||
import SkeletonButton from './Button';
|
||||
import SkeletonAvatar from './Avatar';
|
||||
import SkeletonInput from './Input';
|
||||
|
||||
export { SkeletonProps } from './Skeleton';
|
||||
|
||||
Skeleton.Button = SkeletonButton;
|
||||
Skeleton.Avatar = SkeletonAvatar;
|
||||
Skeleton.Input = SkeletonInput;
|
||||
export default Skeleton;
|
||||
|
@ -15,7 +15,7 @@ To input a value in a range.
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| autoFocus | get focus when component mounted | boolean | false | |
|
||||
| defaultValue | The default value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|number\[] | 0 or \[0, 0] | |
|
||||
| defaultValue | The default value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|\[number, number] | 0 or \[0, 0] | |
|
||||
| disabled | If true, the slider will not be interactable. | boolean | false | |
|
||||
| dots | Whether the thumb can drag over tick only. | boolean | false | |
|
||||
| included | Make effect when `marks` not null, `true` means containment and `false` means coordinative | boolean | true | |
|
||||
@ -26,7 +26,7 @@ To input a value in a range.
|
||||
| reverse | reverse the component | boolean | false | |
|
||||
| step | The granularity the slider can step through values. Must greater than 0, and be divided by (max - min) . When `marks` no null, `step` can be `null`. | number\|null | 1 | |
|
||||
| tipFormatter | Slider will pass its value to `tipFormatter`, and display its value in Tooltip, and hide Tooltip when return value is null. | Function\|null | IDENTITY | |
|
||||
| value | The value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|number\[] | |
|
||||
| value | The value of slider. When `range` is `false`, use `number`, otherwise, use `[number, number]` | number\|\[number, number] | |
|
||||
| vertical | If true, the slider will be vertical. | Boolean | false | |
|
||||
| onAfterChange | Fire when `onmouseup` is fired. | Function(value) | NOOP | |
|
||||
| onChange | Callback function that is fired when the user changes the slider's value. | Function(value) | NOOP | |
|
||||
|
@ -16,7 +16,7 @@ title: Slider
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| allowClear | 支持清除, 单选模式有效 | boolean | false | |
|
||||
| defaultValue | 设置初始取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|number\[] | 0 or \[0, 0] | |
|
||||
| defaultValue | 设置初始取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|\[number, number] | 0 or \[0, 0] | |
|
||||
| disabled | 值为 `true` 时,滑块为禁用状态 | boolean | false | |
|
||||
| dots | 是否只能拖拽到刻度上 | boolean | false | |
|
||||
| included | `marks` 不为空对象时有效,值为 true 时表示值为包含关系,false 表示并列 | boolean | true | |
|
||||
@ -27,7 +27,7 @@ title: Slider
|
||||
| reverse | 反向坐标轴 | boolean | false | |
|
||||
| step | 步长,取值必须大于 0,并且可被 (max - min) 整除。当 `marks` 不为空对象时,可以设置 `step` 为 `null`,此时 Slider 的可选值仅有 marks 标出来的部分。 | number\|null | 1 | |
|
||||
| tipFormatter | Slider 会把当前值传给 `tipFormatter`,并在 Tooltip 中显示 `tipFormatter` 的返回值,若为 null,则隐藏 Tooltip。 | Function\|null | IDENTITY | |
|
||||
| value | 设置当前取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|number\[] | | |
|
||||
| value | 设置当前取值。当 `range` 为 `false` 时,使用 `number`,否则用 `[number, number]` | number\|\[number, number] | | |
|
||||
| vertical | 值为 `true` 时,Slider 为垂直方向 | Boolean | false | |
|
||||
| onAfterChange | 与 `onmouseup` 触发时机一致,把当前值作为参数传入。 | Function(value) | NOOP | |
|
||||
| onChange | 当 Slider 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入。 | Function(value) | NOOP | |
|
||||
|
@ -10,28 +10,14 @@
|
||||
margin: 0 0 0 @steps-desciption-max-width / 2;
|
||||
padding: 0;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin: 0 @steps-desciption-max-width / 2 0 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: ~'calc(100% - 20px)';
|
||||
height: 3px;
|
||||
margin-left: 12px;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin-right: 12px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:first-child .@{steps-prefix-cls}-icon-dot {
|
||||
left: 2px;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: 2px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
&-icon {
|
||||
width: @steps-dot-size;
|
||||
@ -42,11 +28,6 @@
|
||||
background: transparent;
|
||||
border: 0;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin-right: 67px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.@{steps-prefix-cls}-icon-dot {
|
||||
position: relative;
|
||||
float: left;
|
||||
@ -54,10 +35,6 @@
|
||||
height: 100%;
|
||||
border-radius: 100px;
|
||||
transition: all 0.3s;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
float: right;
|
||||
}
|
||||
/* expand hover area */
|
||||
&::after {
|
||||
position: absolute;
|
||||
@ -67,11 +44,6 @@
|
||||
height: 32px;
|
||||
background: fade(@black, 0.1%);
|
||||
content: '';
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: -26px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,11 +66,6 @@
|
||||
margin-top: 8px;
|
||||
margin-left: 0;
|
||||
background: none;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin-right: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
// https://github.com/ant-design/ant-design/issues/18354
|
||||
.@{steps-prefix-cls}-item > .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-tail {
|
||||
@ -106,26 +73,11 @@
|
||||
left: -9px;
|
||||
margin: 0;
|
||||
padding: 22px 0 4px;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: -9px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
.@{steps-prefix-cls}-item:first-child .@{steps-prefix-cls}-icon-dot {
|
||||
left: 0;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
.@{steps-prefix-cls}-item-process .@{steps-prefix-cls}-icon-dot {
|
||||
left: -2px;
|
||||
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: -2px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,3 +174,75 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// progress-dot
|
||||
.@{steps-prefix-cls}-dot,
|
||||
.@{steps-prefix-cls}-dot.@{steps-prefix-cls}-small {
|
||||
.@{steps-prefix-cls}-item {
|
||||
&-tail {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin: 0 @steps-desciption-max-width / 2 0 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin-right: 12px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:first-child .@{steps-prefix-cls}-icon-dot {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: 2px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
&-icon {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin-right: 67px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.@{steps-prefix-cls}-icon-dot {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
float: right;
|
||||
}
|
||||
/* expand hover area */
|
||||
&::after {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: -26px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{steps-prefix-cls}-vertical.@{steps-prefix-cls}-dot {
|
||||
.@{steps-prefix-cls}-item-icon {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
margin-right: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
// https://github.com/ant-design/ant-design/issues/18354
|
||||
.@{steps-prefix-cls}-item > .@{steps-prefix-cls}-item-container > .@{steps-prefix-cls}-item-tail {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: -9px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
.@{steps-prefix-cls}-item:first-child .@{steps-prefix-cls}-icon-dot {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
.@{steps-prefix-cls}-item-process .@{steps-prefix-cls}-icon-dot {
|
||||
.@{steps-prefix-cls}-rtl& {
|
||||
right: -2px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -656,7 +656,8 @@
|
||||
@tabs-card-head-background: @background-color-light;
|
||||
@tabs-card-height: 40px;
|
||||
@tabs-card-active-color: @primary-color;
|
||||
@tabs-card-horizontal-padding: 8px @padding-md;
|
||||
@tabs-card-horizontal-padding: (@tabs-card-height - floor(@font-size-base * @line-height-base)) / 2 -
|
||||
@border-width-base @padding-md;
|
||||
@tabs-card-horizontal-padding-sm: 6px @padding-md;
|
||||
@tabs-title-font-size: @font-size-base;
|
||||
@tabs-title-font-size-lg: @font-size-lg;
|
||||
|
@ -13,10 +13,7 @@ describe('Switch', () => {
|
||||
|
||||
it('should has click wave effect', async () => {
|
||||
const wrapper = mount(<Switch />);
|
||||
wrapper
|
||||
.find('.ant-switch')
|
||||
.getDOMNode()
|
||||
.click();
|
||||
wrapper.find('.ant-switch').getDOMNode().click();
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
@ -27,7 +24,7 @@ describe('Switch', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
mount(<Switch value />);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Switch] `value` is not validate prop, do you mean `checked`?',
|
||||
'Warning: [antd: Switch] `value` is not a valid prop, do you mean `checked`?',
|
||||
);
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ export default class Switch extends React.Component<SwitchProps, {}> {
|
||||
warning(
|
||||
'checked' in props || !('value' in props),
|
||||
'Switch',
|
||||
'`value` is not validate prop, do you mean `checked`?',
|
||||
'`value` is not a valid prop, do you mean `checked`?',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,7 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
||||
rowClassName,
|
||||
columns,
|
||||
children,
|
||||
childrenColumnName: legacyChildrenColumnName,
|
||||
onChange,
|
||||
getPopupContainer,
|
||||
loading,
|
||||
@ -106,7 +107,6 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
||||
expandedRowRender,
|
||||
expandIconColumnIndex,
|
||||
indentSize,
|
||||
childrenColumnName = 'children',
|
||||
scroll,
|
||||
sortDirections,
|
||||
locale,
|
||||
@ -128,9 +128,11 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
||||
const dropdownPrefixCls = getPrefixCls('dropdown', customizeDropdownPrefixCls);
|
||||
|
||||
const mergedExpandable: ExpandableConfig<RecordType> = {
|
||||
childrenColumnName: legacyChildrenColumnName,
|
||||
expandIconColumnIndex,
|
||||
...expandable,
|
||||
};
|
||||
const { childrenColumnName = 'children' } = mergedExpandable;
|
||||
|
||||
const expandType: ExpandType = React.useMemo<ExpandType>(() => {
|
||||
if (rawData.some(item => (item as any)[childrenColumnName])) {
|
||||
|
@ -779,4 +779,22 @@ describe('Table.rowSelection', () => {
|
||||
jest.runAllTimers();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Table selection should check', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(
|
||||
<Table
|
||||
dataSource={[{ name: 'light', sub: [{ name: 'bamboo' }] }]}
|
||||
expandable={{ expandedRowKeys: ['light'], childrenColumnName: 'sub' }}
|
||||
rowSelection={{ onChange }}
|
||||
rowKey="name"
|
||||
/>,
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('input')
|
||||
.last()
|
||||
.simulate('change', { target: { checked: true } });
|
||||
expect(onChange.mock.calls[0][1]).toEqual([expect.objectContaining({ name: 'bamboo' })]);
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,3 @@
|
||||
/* eslint-disable no-unused-expressions */
|
||||
|
||||
import * as React from 'react';
|
||||
import Table from '../Table';
|
||||
import { ColumnProps } from '..';
|
||||
@ -44,4 +42,3 @@ describe('Table.typescript types', () => {
|
||||
expect(columns).toBeTruthy();
|
||||
});
|
||||
});
|
||||
/* eslint-enable */
|
||||
|
@ -85,7 +85,7 @@ const EditableTable = () => {
|
||||
const isEditing = (record: Item) => record.key === editingKey;
|
||||
|
||||
const edit = (record: Item) => {
|
||||
form.setFieldsValue({ ...record });
|
||||
form.setFieldsValue({ name: '', age: '', address: '', ...record });
|
||||
setEditingKey(record.key);
|
||||
};
|
||||
|
||||
|
@ -66,7 +66,7 @@ const columns = [
|
||||
| dataSource | Data record array to be displayed | any\[] | - |
|
||||
| expandable | Config expandable content | [expandable](#expandable) | - |
|
||||
| footer | Table footer renderer | Function(currentPageData) | - |
|
||||
| loading | Loading status of table | boolean\|[object](https://ant.design/components/spin-cn/#API) ([more](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | `false` |
|
||||
| loading | Loading status of table | boolean\|[object](/components/spin/#API) ([more](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | `false` |
|
||||
| locale | i18n text including filter, sort, empty text, etc | object | filterConfirm: 'Ok' <br> filterReset: 'Reset' <br> emptyText: 'No Data' <br> [Default](https://github.com/ant-design/ant-design/issues/575#issuecomment-159169511) |
|
||||
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | - |
|
||||
| rowClassName | Row's className | Function(record, index):string | - |
|
||||
|
@ -71,7 +71,7 @@ const columns = [
|
||||
| dataSource | 数据数组 | any\[] | - |
|
||||
| expandable | 配置展开属性 | [expandable](#expandable) | - |
|
||||
| footer | 表格尾部 | Function(currentPageData) | - |
|
||||
| loading | 页面是否加载中 | boolean\|[object](https://ant.design/components/spin-cn/#API) ([更多](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | false |
|
||||
| loading | 页面是否加载中 | boolean\|[object](/components/spin/#API) ([更多](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | false |
|
||||
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: '确定' <br> filterReset: '重置' <br> emptyText: '暂无数据' <br> [默认值](https://github.com/ant-design/ant-design/issues/575#issuecomment-159169511) |
|
||||
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - |
|
||||
| rowClassName | 表格行的类名 | Function(record, index):string | - |
|
||||
|
@ -507,6 +507,8 @@
|
||||
// ============================ Fixed =============================
|
||||
&-cell-fix-left,
|
||||
&-cell-fix-right {
|
||||
position: -webkit-sticky !important;
|
||||
position: sticky !important;
|
||||
z-index: 2;
|
||||
background: @table-bg;
|
||||
}
|
||||
|
@ -261,10 +261,10 @@
|
||||
.@{tab-prefix-cls}-top-content,
|
||||
.@{tab-prefix-cls}-bottom-content {
|
||||
width: 100%;
|
||||
|
||||
> .@{tab-prefix-cls}-tabpane {
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
-webkit-backface-visibility: hidden;
|
||||
opacity: 1;
|
||||
transition: opacity 0.45s;
|
||||
}
|
||||
|
@ -222,6 +222,51 @@ exports[`renders ./components/time-picker/demo/basic.md correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/time-picker/demo/colored-popup.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-picker"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-input"
|
||||
>
|
||||
<input
|
||||
placeholder="Select time"
|
||||
readonly=""
|
||||
size="10"
|
||||
title=""
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
class="ant-picker-suffix"
|
||||
>
|
||||
<span
|
||||
aria-label="clock-circle"
|
||||
class="anticon anticon-clock-circle"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="clock-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
|
||||
/>
|
||||
<path
|
||||
d="M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/time-picker/demo/disabled.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-picker ant-picker-disabled"
|
||||
|
@ -61,4 +61,15 @@ describe('TimePicker', () => {
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should pass popupClassName prop to Picker as dropdownClassName prop', () => {
|
||||
const popupClassName = 'myCustomClassName';
|
||||
const wrapper = mount(
|
||||
<TimePicker
|
||||
defaultOpenValue={moment('00:00:00', 'HH:mm:ss')}
|
||||
popupClassName={popupClassName}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.find('Picker').prop('dropdownClassName')).toEqual(popupClassName);
|
||||
});
|
||||
});
|
||||
|
39
components/time-picker/demo/colored-popup.md
Normal file
39
components/time-picker/demo/colored-popup.md
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
order: 9
|
||||
title:
|
||||
zh-CN: 色付きポップアップ
|
||||
en-US: Colored Popup
|
||||
debug: true
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
カスタムクラスを `TimePicker`ポップアップに渡す
|
||||
|
||||
## en-US
|
||||
|
||||
Passing custom class to `TimePicker` popup
|
||||
|
||||
```jsx
|
||||
import { TimePicker } from 'antd';
|
||||
import moment from 'moment';
|
||||
|
||||
const onChange = (time, timeString) => {
|
||||
console.log(time, timeString);
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<TimePicker
|
||||
onChange={onChange}
|
||||
defaultOpenValue={moment('00:00:00', 'HH:mm:ss')}
|
||||
popupClassName="myCustomClassName"
|
||||
/>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
```css
|
||||
.myCustomClassName .ant-picker-time-panel-cell-inner {
|
||||
color: red !important;
|
||||
}
|
||||
```
|
@ -41,7 +41,7 @@ import moment from 'moment';
|
||||
| minuteStep | interval between minutes in picker | number | 1 | |
|
||||
| open | whether to popup panel | boolean | false | |
|
||||
| placeholder | display when there's no value | string | "Select a time" | |
|
||||
| popupClassName | className of panel | string | '' | |
|
||||
| popupClassName | className of panel | string | - | |
|
||||
| popupStyle | style of panel | object | - | |
|
||||
| secondStep | interval between seconds in picker | number | 1 | |
|
||||
| suffixIcon | The custom suffix icon | ReactNode | - | |
|
||||
|
@ -20,10 +20,11 @@ const RangePicker = React.forwardRef<any, TimeRangePickerProps>((props, ref) =>
|
||||
|
||||
export interface TimePickerProps extends Omit<PickerTimeProps<Moment>, 'picker'> {
|
||||
addon?: () => React.ReactNode;
|
||||
popupClassName?: string;
|
||||
}
|
||||
|
||||
const TimePicker = React.forwardRef<any, TimePickerProps>(
|
||||
({ addon, renderExtraFooter, ...restProps }, ref) => {
|
||||
({ addon, renderExtraFooter, popupClassName, ...restProps }, ref) => {
|
||||
const internalRenderExtraFooter = React.useMemo(() => {
|
||||
if (renderExtraFooter) {
|
||||
return renderExtraFooter;
|
||||
@ -42,6 +43,7 @@ const TimePicker = React.forwardRef<any, TimePickerProps>(
|
||||
return (
|
||||
<InternalTimePicker
|
||||
{...restProps}
|
||||
dropdownClassName={popupClassName}
|
||||
mode={undefined}
|
||||
ref={ref}
|
||||
renderExtraFooter={internalRenderExtraFooter}
|
||||
|
@ -42,7 +42,7 @@ import moment from 'moment';
|
||||
| minuteStep | 分钟选项间隔 | number | 1 | |
|
||||
| open | 面板是否打开 | boolean | false | |
|
||||
| placeholder | 没有值的时候显示的内容 | string | "请选择时间" | |
|
||||
| popupClassName | 弹出层类名 | string | '' | |
|
||||
| popupClassName | 弹出层类名 | string | - | |
|
||||
| popupStyle | 弹出层样式对象 | object | - | |
|
||||
| secondStep | 秒选项间隔 | number | 1 | |
|
||||
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
|
||||
|
@ -128,18 +128,8 @@ describe('Tooltip', () => {
|
||||
</Button>
|
||||
</Tooltip>,
|
||||
);
|
||||
expect(
|
||||
wrapper1
|
||||
.find('span')
|
||||
.first()
|
||||
.getDOMNode().style.display,
|
||||
).toBe('inline-block');
|
||||
expect(
|
||||
wrapper2
|
||||
.find('span')
|
||||
.first()
|
||||
.getDOMNode().style.display,
|
||||
).toBe('block');
|
||||
expect(wrapper1.find('span').first().getDOMNode().style.display).toBe('inline-block');
|
||||
expect(wrapper2.find('span').first().getDOMNode().style.display).toBe('block');
|
||||
});
|
||||
|
||||
it('should works for arrowPointAtCenter', () => {
|
||||
@ -161,10 +151,7 @@ describe('Tooltip', () => {
|
||||
</button>
|
||||
</Tooltip>,
|
||||
);
|
||||
wrapper
|
||||
.find('button')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
wrapper.find('button').at(0).simulate('click');
|
||||
const popupLeftDefault = parseInt(wrapper.instance().getPopupDomNode().style.left, 10);
|
||||
|
||||
const wrapper2 = mount(
|
||||
@ -181,10 +168,7 @@ describe('Tooltip', () => {
|
||||
</button>
|
||||
</Tooltip>,
|
||||
);
|
||||
wrapper2
|
||||
.find('button')
|
||||
.at(0)
|
||||
.simulate('click');
|
||||
wrapper2.find('button').at(0).simulate('click');
|
||||
const popupLeftArrowPointAtCenter = parseInt(
|
||||
wrapper2.instance().getPopupDomNode().style.left,
|
||||
10,
|
||||
@ -272,22 +256,26 @@ describe('Tooltip', () => {
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('support other placement', async () => {
|
||||
it('support other placement', done => {
|
||||
const wrapper = mount(
|
||||
<Tooltip
|
||||
title="xxxxx"
|
||||
placement="bottomLeft"
|
||||
visible
|
||||
transitionName=""
|
||||
mouseEnterDelay={0}
|
||||
afterVisibleChange={visible => {
|
||||
if (visible) {
|
||||
expect(wrapper.find('Trigger').props().popupPlacement).toBe('bottomLeft');
|
||||
}
|
||||
done();
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
Hello world!
|
||||
</span>
|
||||
<span>Hello world!</span>
|
||||
</Tooltip>,
|
||||
);
|
||||
await sleep(1000);
|
||||
expect(wrapper.instance().getPopupDomNode().className).toContain('placement-bottomLeft');
|
||||
expect(wrapper.find('span')).toHaveLength(1);
|
||||
const button = wrapper.find('span').at(0);
|
||||
button.simulate('mouseenter');
|
||||
});
|
||||
|
||||
it('other placement when mouse enter', async () => {
|
||||
@ -296,18 +284,17 @@ describe('Tooltip', () => {
|
||||
title="xxxxx"
|
||||
placement="topRight"
|
||||
transitionName=""
|
||||
popupTransitionName=""
|
||||
mouseEnterDelay={0}
|
||||
>
|
||||
<span>
|
||||
Hello world!
|
||||
</span>
|
||||
<span>Hello world!</span>
|
||||
</Tooltip>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('span')).toHaveLength(1);
|
||||
const button = wrapper.find('span').at(0);
|
||||
button.simulate('mouseenter');
|
||||
await sleep(300);
|
||||
await sleep(500);
|
||||
expect(wrapper.instance().getPopupDomNode().className).toContain('placement-topRight');
|
||||
});
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user