Browse Source

support prettier

pull/13476/head
陈帅 6 years ago
committed by 偏右
parent
commit
dca9715186
  1. 27
      .eslintrc.js
  2. 7
      .prettierignore
  3. 19
      .prettierrc
  4. 2
      .stylelintrc
  5. 2
      components/__tests__/index.test.js
  6. 12
      components/_util/__tests__/util.test.js
  7. 6
      components/_util/isFlexSupported.tsx
  8. 4
      components/_util/raf.ts
  9. 2
      components/_util/warning.tsx
  10. 39
      components/_util/wave.tsx
  11. 31
      components/affix/__tests__/Affix.test.js
  12. 38
      components/affix/index.tsx
  13. 2
      components/affix/style/index.less
  14. 14
      components/alert/__tests__/index.test.js
  15. 63
      components/alert/index.tsx
  16. 12
      components/alert/style/index.less
  17. 54
      components/anchor/Anchor.tsx
  18. 9
      components/anchor/AnchorLink.tsx
  19. 24
      components/anchor/__tests__/Anchor.test.js
  20. 8
      components/anchor/style/index.less
  21. 22
      components/auto-complete/InputElement.tsx
  22. 11
      components/auto-complete/__tests__/index.test.js
  23. 92
      components/auto-complete/index.tsx
  24. 12
      components/auto-complete/style/index.less
  25. 4
      components/avatar/__tests__/Avatar.test.js
  26. 61
      components/avatar/index.tsx
  27. 6
      components/avatar/style/index.less
  28. 12
      components/back-top/index.tsx
  29. 13
      components/back-top/style/index.less
  30. 98
      components/badge/ScrollNumber.tsx
  31. 16
      components/badge/__tests__/index.test.js
  32. 40
      components/badge/index.tsx
  33. 16
      components/badge/style/index.less
  34. 27
      components/breadcrumb/Breadcrumb.tsx
  35. 17
      components/breadcrumb/BreadcrumbItem.tsx
  36. 8
      components/breadcrumb/__tests__/Breadcrumb.test.js
  37. 171
      components/breadcrumb/__tests__/router.test.js
  38. 8
      components/breadcrumb/style/index.less
  39. 90
      components/button/__tests__/index.test.js
  40. 12
      components/button/button-group.tsx
  41. 49
      components/button/button.tsx
  42. 25
      components/button/style/index.less
  43. 32
      components/button/style/mixin.less
  44. 18
      components/calendar/Header.tsx
  45. 105
      components/calendar/__tests__/index.test.js
  46. 70
      components/calendar/index.tsx
  47. 12
      components/calendar/style/index.less
  48. 17
      components/card/Meta.tsx
  49. 12
      components/card/__tests__/index.test.js
  50. 80
      components/card/index.tsx
  51. 35
      components/card/style/index.less
  52. 30
      components/carousel/__tests__/index.test.js
  53. 10
      components/carousel/index.tsx
  54. 14
      components/carousel/style/index.less
  55. 363
      components/cascader/__tests__/index.test.js
  56. 182
      components/cascader/index.tsx
  57. 12
      components/cascader/style/index.less
  58. 16
      components/checkbox/Checkbox.tsx
  59. 9
      components/checkbox/Group.tsx
  60. 7
      components/checkbox/__tests__/checkbox.test.js
  61. 78
      components/checkbox/__tests__/group.test.js
  62. 4
      components/checkbox/style/index.less
  63. 14
      components/checkbox/style/mixin.less
  64. 17
      components/collapse/Collapse.tsx
  65. 9
      components/collapse/CollapsePanel.tsx
  66. 12
      components/collapse/style/index.less
  67. 40
      components/comment/index.tsx
  68. 10
      components/comment/style/index.less
  69. 14
      components/config-provider/index.tsx
  70. 2
      components/config-provider/style/index.less
  71. 131
      components/date-picker/RangePicker.tsx
  72. 98
      components/date-picker/WeekPicker.tsx
  73. 52
      components/date-picker/__tests__/DatePicker.test.js
  74. 14
      components/date-picker/__tests__/MonthPicker.test.js
  75. 226
      components/date-picker/__tests__/RangePicker.test.js
  76. 4
      components/date-picker/__tests__/WeekPicker.test.js
  77. 31
      components/date-picker/__tests__/focus.test.js
  78. 26
      components/date-picker/__tests__/other.test.js
  79. 110
      components/date-picker/__tests__/showTime.test.js
  80. 9
      components/date-picker/__tests__/utils.js
  81. 65
      components/date-picker/createPicker.tsx
  82. 4
      components/date-picker/index.tsx
  83. 31
      components/date-picker/interface.tsx
  84. 5
      components/date-picker/locale/example.json
  85. 2
      components/date-picker/locale/it_IT.tsx
  86. 8
      components/date-picker/style/Calendar.less
  87. 4
      components/date-picker/style/DecadePanel.less
  88. 7
      components/date-picker/style/MonthPanel.less
  89. 6
      components/date-picker/style/Picker.less
  90. 4
      components/date-picker/style/RangePicker.less
  91. 2
      components/date-picker/style/TimePicker.less
  92. 2
      components/date-picker/style/WeekPicker.less
  93. 7
      components/date-picker/style/YearPanel.less
  94. 30
      components/date-picker/style/index.less
  95. 40
      components/date-picker/wrapPicker.tsx
  96. 14
      components/divider/index.tsx
  97. 8
      components/divider/style/index.less
  98. 50
      components/drawer/__tests__/Drawer.test.js
  99. 10
      components/drawer/__tests__/DrawerEvent.test.js
  100. 6
      components/drawer/__tests__/MultiDrawer.test.js

27
.eslintrc.js

@ -1,5 +1,5 @@
const eslintrc = {
extends: ['eslint-config-airbnb'],
extends: ['airbnb', 'prettier'],
env: {
browser: true,
node: true,
@ -8,18 +8,27 @@ const eslintrc = {
es6: true,
},
parser: 'babel-eslint',
plugins: [
'markdown',
'react',
'babel',
],
plugins: ['markdown', 'react', 'babel'],
rules: {
'react/jsx-one-expression-per-line': 0,
'react/prop-types': 0,
'react/forbid-prop-types': 0,
'import/no-extraneous-dependencies': ['error', {
devDependencies: ['site/**', 'tests/**', 'scripts/**', '**/*.test.js', '**/__tests__/*', '*.config.js', '**/*.md'],
}],
'react/jsx-indent': 0,
'react/jsx-wrap-multilines': ['error', { declaration: false, assignment: false }],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'site/**',
'tests/**',
'scripts/**',
'**/*.test.js',
'**/__tests__/*',
'*.config.js',
'**/*.md',
],
},
],
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', '.md'] }],
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/anchor-has-content': 0,

7
.prettierignore

@ -0,0 +1,7 @@
**/*.md
**/*.svg
**/*.ejs
**/*.html
package.json
.umi
.umi-production

19
.prettierrc

@ -0,0 +1,19 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"overrides": [
{
"files": ".prettierrc",
"options": {
"parser": "json"
}
},
{
"files": ".stylelintrc",
"options": {
"parser": "json"
}
}
]
}

2
.stylelintrc

@ -1,5 +1,5 @@
{
"extends": "stylelint-config-standard",
"extends": ["stylelint-config-standard", "stylelint-config-prettier"],
"rules": {
"comment-empty-line-before": null,
"declaration-empty-line-before": null,

2
components/__tests__/index.test.js

@ -14,7 +14,7 @@ describe('antd', () => {
it('should hint when import all components in dev mode', () => {
expect(warnSpy).toBeCalledWith(
'You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.'
'You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.',
);
warnSpy.mockRestore();
});

12
components/_util/__tests__/util.test.js

@ -88,7 +88,7 @@ describe('Test utils function', () => {
});
});
it('delayRaf', (done) => {
it('delayRaf', done => {
jest.useRealTimers();
let bamboo = false;
@ -118,9 +118,13 @@ describe('Test utils function', () => {
it('triggerEvent', () => {
const button = document.createElement('button');
button.addEventListener('click', () => {
button.style.width = '100px';
}, true);
button.addEventListener(
'click',
() => {
button.style.width = '100px';
},
true,
);
triggerEvent(button, 'click');
expect(button.style.width).toBe('100px');
});

6
components/_util/isFlexSupported.tsx

@ -1,10 +1,12 @@
export default function isFlexSupported() {
if (typeof window !== 'undefined' && window.document && window.document.documentElement) {
const { documentElement } = window.document;
return 'flex' in documentElement.style ||
return (
'flex' in documentElement.style ||
'webkitFlex' in documentElement.style ||
'Flex' in documentElement.style ||
'msFlex' in documentElement.style;
'msFlex' in documentElement.style
);
}
return false;
}

4
components/_util/raf.ts

@ -1,7 +1,7 @@
import raf from 'raf';
interface RafMap {
[id: number]: number,
[id: number]: number;
}
let id: number = 0;
@ -31,4 +31,4 @@ export default function wrapperRaf(callback: () => void, delayFrames: number = 1
wrapperRaf.cancel = function(id: number) {
raf.cancel(ids[id]);
delete ids[id];
}
};

2
components/_util/warning.tsx

@ -1,6 +1,6 @@
import warning from 'warning';
const warned: { [msg: string]: boolean} = {};
const warned: { [msg: string]: boolean } = {};
export default (valid: boolean, message: string): void => {
if (!valid && !warned[message]) {
warning(false, message);

39
components/_util/wave.tsx

@ -13,7 +13,7 @@ function isHidden(element: HTMLElement) {
return !element || element.offsetParent === null;
}
export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
export default class Wave extends React.Component<{ insertExtraNode?: boolean }> {
private instance?: {
cancel: () => void;
};
@ -22,7 +22,7 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
private clickWaveTimeoutId: number;
private animationStartId: number;
private animationStart: boolean = false;
private destroy: boolean = false
private destroy: boolean = false;
isNotGrey(color: string) {
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/);
@ -45,15 +45,16 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
node.setAttribute(attributeName, 'true');
// Not white or transparnt or grey
styleForPesudo = styleForPesudo || document.createElement('style');
if (waveColor &&
waveColor !== '#ffffff' &&
waveColor !== 'rgb(255, 255, 255)' &&
this.isNotGrey(waveColor) &&
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
waveColor !== 'transparent') {
if (
waveColor &&
waveColor !== '#ffffff' &&
waveColor !== 'rgb(255, 255, 255)' &&
this.isNotGrey(waveColor) &&
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
waveColor !== 'transparent'
) {
extraNode.style.borderColor = waveColor;
styleForPesudo.innerHTML =
`[ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }`;
styleForPesudo.innerHTML = `[ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }`;
if (!document.body.contains(styleForPesudo)) {
document.body.appendChild(styleForPesudo);
}
@ -63,13 +64,15 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
}
TransitionEvents.addStartEventListener(node, this.onTransitionStart);
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
}
};
bindAnimationEvent = (node: HTMLElement) => {
if (!node ||
!node.getAttribute ||
node.getAttribute('disabled') ||
node.className.indexOf('disabled') >= 0) {
if (
!node ||
!node.getAttribute ||
node.getAttribute('disabled') ||
node.className.indexOf('disabled') >= 0
) {
return;
}
const onClick = (e: MouseEvent) => {
@ -99,7 +102,7 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
node.removeEventListener('click', onClick, true);
},
};
}
};
getAttributeName() {
const { insertExtraNode } = this.props;
@ -132,14 +135,14 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
if (!this.animationStart) {
this.resetEffect(node);
}
}
};
onTransitionEnd = (e: AnimationEvent) => {
if (!e || e.animationName !== 'fadeEffect') {
return;
}
this.resetEffect(e.target as HTMLElement);
}
};
removeExtraStyleNode() {
if (styleForPesudo) {

31
components/affix/__tests__/Affix.test.js

@ -12,7 +12,7 @@ class AffixMounter extends React.Component {
});
}
getTarget = () => this.container
getTarget = () => this.container;
render() {
return (
@ -21,7 +21,9 @@ class AffixMounter extends React.Component {
height: 100,
overflowY: 'scroll',
}}
ref={(node) => { this.container = node; }}
ref={node => {
this.container = node;
}}
>
<div
className="background"
@ -32,12 +34,12 @@ class AffixMounter extends React.Component {
>
<Affix
target={() => this.container}
ref={(ele) => { this.affix = ele; }}
ref={ele => {
this.affix = ele;
}}
{...this.props}
>
<Button type="primary">
Fixed at the top of container
</Button>
<Button type="primary">Fixed at the top of container</Button>
</Affix>
</div>
</div>
@ -56,9 +58,14 @@ describe('Affix Render', () => {
jest.useRealTimers();
});
const scrollTo = (top) => {
const scrollTo = top => {
wrapper.instance().affix.fixedNode.parentNode.getBoundingClientRect = jest.fn(() => ({
bottom: 100, height: 28, left: 0, right: 0, top: 50 - top, width: 195,
bottom: 100,
height: 28,
left: 0,
right: 0,
top: 50 - top,
width: 195,
}));
wrapper.instance().container.scrollTop = top;
events.scroll({
@ -86,7 +93,9 @@ describe('Affix Render', () => {
it('support offsetBottom', () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter offsetBottom={0} />, { attachTo: document.getElementById('mounter') });
wrapper = mount(<AffixMounter offsetBottom={0} />, {
attachTo: document.getElementById('mounter'),
});
jest.runAllTimers();
scrollTo(0);
@ -102,7 +111,9 @@ describe('Affix Render', () => {
it('updatePosition when offsetTop changed', () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter offsetTop={0} />, { attachTo: document.getElementById('mounter') });
wrapper = mount(<AffixMounter offsetTop={0} />, {
attachTo: document.getElementById('mounter'),
});
jest.runAllTimers();
scrollTo(100);

38
components/affix/index.tsx

@ -9,9 +9,9 @@ import getScroll from '../_util/getScroll';
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
function getTargetRect(target: HTMLElement | Window | null): ClientRect {
return target !== window ?
(target as HTMLElement).getBoundingClientRect() :
{ top: 0, left: 0, bottom: 0 } as ClientRect;
return target !== window
? (target as HTMLElement).getBoundingClientRect()
: ({ top: 0, left: 0, bottom: 0 } as ClientRect);
}
function getOffset(element: HTMLElement, target: HTMLElement | Window | null) {
@ -26,10 +26,8 @@ function getOffset(element: HTMLElement, target: HTMLElement | Window | null) {
const clientLeft = docElem.clientLeft || 0;
return {
top: elemRect.top - targetRect.top +
scrollTop - clientTop,
left: elemRect.left - targetRect.left +
scrollLeft - clientLeft,
top: elemRect.top - targetRect.top + scrollTop - clientTop,
left: elemRect.left - targetRect.left + scrollLeft - clientLeft,
width: elemRect.width,
height: elemRect.height,
};
@ -101,8 +99,7 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
}
this.setState({ affixStyle: affixStyle as React.CSSProperties }, () => {
const affixed = !!this.state.affixStyle;
if ((affixStyle && !originalAffixStyle) ||
(!affixStyle && originalAffixStyle)) {
if ((affixStyle && !originalAffixStyle) || (!affixStyle && originalAffixStyle)) {
onChange(affixed);
}
});
@ -181,10 +178,10 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
});
} else if (
scrollTop < elemOffset.top + elemSize.height + (offsetBottom as number) - targetInnerHeight &&
offsetMode.bottom
offsetMode.bottom
) {
// Fixed Bottom
const targetBottomOffet = targetNode === window ? 0 : (window.innerHeight - targetRect.bottom);
const targetBottomOffet = targetNode === window ? 0 : window.innerHeight - targetRect.bottom;
const width = elemOffset.width;
this.setAffixStyle(e, {
position: 'fixed',
@ -198,7 +195,12 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
});
} else {
const { affixStyle } = this.state;
if (e.type === 'resize' && affixStyle && affixStyle.position === 'fixed' && affixNode.offsetWidth) {
if (
e.type === 'resize' &&
affixStyle &&
affixStyle.position === 'fixed' &&
affixNode.offsetWidth
) {
this.setAffixStyle(e, { ...affixStyle, width: affixNode.offsetWidth });
} else {
this.setAffixStyle(e, null);
@ -264,18 +266,24 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
saveFixedNode = (node: HTMLDivElement) => {
this.fixedNode = node;
}
};
savePlaceholderNode = (node: HTMLDivElement) => {
this.placeholderNode = node;
}
};
render() {
const className = classNames({
[this.props.prefixCls || 'ant-affix']: this.state.affixStyle,
});
const props = omit(this.props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target', 'onChange']);
const props = omit(this.props, [
'prefixCls',
'offsetTop',
'offsetBottom',
'target',
'onChange',
]);
const placeholderStyle = { ...this.state.placeholderStyle, ...this.props.style };
return (
<div {...props} style={placeholderStyle} ref={this.savePlaceholderNode}>

2
components/affix/style/index.less

@ -1,4 +1,4 @@
@import "../../style/themes/default";
@import '../../style/themes/default';
.@{ant-prefix}-affix {
position: fixed;

14
components/alert/__tests__/index.test.js

@ -21,7 +21,7 @@ describe('Alert', () => {
closable
onClose={onClose}
afterClose={afterClose}
/>
/>,
);
wrapper.find('.ant-alert-close-icon').simulate('click');
expect(onClose).toBeCalled();
@ -31,26 +31,20 @@ describe('Alert', () => {
describe('data and aria props', () => {
it('sets data attributes on input', () => {
const wrapper = mount(
<Alert data-test="test-id" data-id="12345" />
);
const wrapper = mount(<Alert data-test="test-id" data-id="12345" />);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('data-test')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
});
it('sets aria attributes on input', () => {
const wrapper = mount(
<Alert aria-describedby="some-label" />
);
const wrapper = mount(<Alert aria-describedby="some-label" />);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('aria-describedby')).toBe('some-label');
});
it('sets role attribute on input', () => {
const wrapper = mount(
<Alert role="status" />
);
const wrapper = mount(<Alert role="status" />);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('role')).toBe('status');
});

63
components/alert/index.tsx

@ -5,7 +5,7 @@ import Icon, { ThemeType } from '../icon';
import classNames from 'classnames';
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
function noop() { }
function noop() {}
export interface AlertProps {
/**
@ -35,8 +35,8 @@ export interface AlertProps {
}
export interface AlertState {
closing: boolean,
closed: boolean
closing: boolean;
closed: boolean;
}
export default class Alert extends React.Component<AlertProps, AlertState> {
@ -57,7 +57,7 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
closing: false,
});
(this.props.onClose || noop)(e);
}
};
animationEnd = () => {
this.setState({
@ -65,12 +65,18 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
closing: true,
});
(this.props.afterClose || noop)();
}
};
render() {
const {
description, prefixCls = 'ant-alert', message, closeText, banner,
className = '', style, icon,
description,
prefixCls = 'ant-alert',
message,
closeText,
banner,
className = '',
style,
icon,
} = this.props;
let { closable, type, showIcon, iconType } = this.props;
@ -111,13 +117,18 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
closable = true;
}
const alertCls = classNames(prefixCls, `${prefixCls}-${type}`, {
[`${prefixCls}-close`]: !this.state.closing,
[`${prefixCls}-with-description`]: !!description,
[`${prefixCls}-no-icon`]: !showIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-closable`]: closable,
}, className);
const alertCls = classNames(
prefixCls,
`${prefixCls}-${type}`,
{
[`${prefixCls}-close`]: !this.state.closing,
[`${prefixCls}-with-description`]: !!description,
[`${prefixCls}-no-icon`]: !showIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-closable`]: closable,
},
className,
);
const closeIcon = closable ? (
<a onClick={this.handleClose} className={`${prefixCls}-close-icon`}>
@ -127,19 +138,17 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
const dataOrAriaProps = getDataOrAriaProps(this.props);
const iconNode = icon && (
React.isValidElement<{ className?: string }>(icon)
? React.cloneElement(
icon,
{
className: classNames({
[icon.props.className as string]: icon.props.className,
[`${prefixCls}-icon`]: true,
}),
},
) : <span className={`${prefixCls}-icon`}>{icon}</span>) || (
<Icon className={`${prefixCls}-icon`} type={iconType} theme={iconTheme} />
);
const iconNode = (icon &&
(React.isValidElement<{ className?: string }>(icon) ? (
React.cloneElement(icon, {
className: classNames({
[icon.props.className as string]: icon.props.className,
[`${prefixCls}-icon`]: true,
}),
})
) : (
<span className={`${prefixCls}-icon`}>{icon}</span>
))) || <Icon className={`${prefixCls}-icon`} type={iconType} theme={iconTheme} />;
return this.state.closed ? null : (
<Animate

12
components/alert/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@alert-prefix-cls: ~"@{ant-prefix}-alert";
@alert-prefix-cls: ~'@{ant-prefix}-alert';
@alert-message-color: @heading-color;
@alert-text-color: @text-color;
@ -76,7 +76,7 @@
.@{iconfont-css-prefix}-close {
color: @alert-close-color;
transition: color .3s;
transition: color 0.3s;
&:hover {
color: #404040;
}
@ -131,12 +131,12 @@
margin: 0;
padding-top: 0;
padding-bottom: 0;
transition: all .3s @ease-in-out-circ;
transition: all 0.3s @ease-in-out-circ;
transform-origin: 50% 0;
}
&-slide-up-leave {
animation: antAlertSlideUpOut .3s @ease-in-out-circ;
animation: antAlertSlideUpOut 0.3s @ease-in-out-circ;
animation-fill-mode: both;
}

54
components/anchor/Anchor.tsx

@ -38,17 +38,24 @@ function easeInOutCubic(t: number, b: number, c: number, d: number) {
const cc = c - b;
t /= d / 2;
if (t < 1) {
return cc / 2 * t * t * t + b;
return (cc / 2) * t * t * t + b;
}
return cc / 2 * ((t -= 2) * t * t + 2) + b;
return (cc / 2) * ((t -= 2) * t * t + 2) + b;
}
const sharpMatcherRegx = /#([^#]+)$/;
function scrollTo(href: string, offsetTop = 0, getContainer: () => AnchorContainer, callback = () => { }) {
function scrollTo(
href: string,
offsetTop = 0,
getContainer: () => AnchorContainer,
callback = () => {},
) {
const container = getContainer();
const scrollTop = getScroll(container, true);
const sharpLinkMatch = sharpMatcherRegx.exec(href);
if (!sharpLinkMatch) { return; }
if (!sharpLinkMatch) {
return;
}
const targetElement = document.getElementById(sharpLinkMatch[1]);
if (!targetElement) {
return;
@ -79,7 +86,7 @@ type Section = {
top: number;
};
export type AnchorContainer = HTMLElement | Window;
export type AnchorContainer = HTMLElement | Window;
export interface AnchorProps {
prefixCls?: string;
@ -91,7 +98,10 @@ export interface AnchorProps {
affix?: boolean;
showInkInFixed?: boolean;
getContainer?: () => AnchorContainer;
onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode, href: string }) => void;
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { title: React.ReactNode; href: string },
) => void;
}
export interface AnchorState {
@ -110,7 +120,10 @@ export interface AntAnchor {
unregisterLink: (link: string) => void;
activeLink: string | null;
scrollTo: (link: string) => void;
onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode, href: string }) => void;
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { title: React.ReactNode; href: string },
) => void;
}
export default class Anchor extends React.Component<AnchorProps, AnchorState> {
@ -181,7 +194,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
this.setState({
activeLink: this.getCurrentAnchor(offsetTop, bounds),
});
}
};
handleScrollTo = (link: string) => {
const { offsetTop, getContainer } = this.props as AnchorDefaultProps;
@ -190,7 +203,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
scrollTo(link, offsetTop, getContainer, () => {
this.animating = false;
});
}
};
getCurrentAnchor(offsetTop = 0, bounds = 5): string {
const activeLink = '';
@ -203,7 +216,9 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
const container = getContainer();
this.links.forEach(link => {
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
if (!sharpLinkMatch) { return; }
if (!sharpLinkMatch) {
return;
}
const target = document.getElementById(sharpLinkMatch[1]);
if (target) {
const top = getOffsetTop(target, container);
@ -217,7 +232,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
});
if (linkSections.length) {
const maxSection = linkSections.reduce((prev, curr) => curr.top > prev.top ? curr : prev);
const maxSection = linkSections.reduce((prev, curr) => (curr.top > prev.top ? curr : prev));
return maxSection.link;
}
return '';
@ -233,11 +248,11 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
if (linkNode) {
this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
}
}
};
saveInkNode = (node: HTMLSpanElement) => {
this.inkNode = node;
}
};
render() {
const {
@ -259,7 +274,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
const wrapperClass = classNames(className, `${prefixCls}-wrapper`);
const anchorClass = classNames(prefixCls, {
'fixed': !affix && !showInkInFixed,
fixed: !affix && !showInkInFixed,
});
const wrapperStyle = {
@ -268,12 +283,9 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
};
const anchorContent = (
<div
className={wrapperClass}
style={wrapperStyle}
>
<div className={wrapperClass} style={wrapperStyle}>
<div className={anchorClass}>
<div className={`${prefixCls}-ink`} >
<div className={`${prefixCls}-ink`}>
<span className={inkClass} ref={this.saveInkNode} />
</div>
{children}
@ -281,7 +293,9 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
</div>
);
return !affix ? anchorContent : (
return !affix ? (
anchorContent
) : (
<Affix offsetTop={offsetTop} target={getContainer}>
{anchorContent}
</Affix>

9
components/anchor/AnchorLink.tsx

@ -47,15 +47,10 @@ export default class AnchorLink extends React.Component<AnchorLinkProps, any> {
onClick(e, { title, href });
}
scrollTo(href);
}
};
render() {
const {
prefixCls,
href,
title,
children,
} = this.props;
const { prefixCls, href, title, children } = this.props;
const active = this.context.antAnchor.activeLink === href;
const wrapperClassName = classNames(`${prefixCls}-link`, {
[`${prefixCls}-link-active`]: active,

24
components/anchor/__tests__/Anchor.test.js

@ -9,7 +9,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>
</Anchor>,
);
wrapper.find('a[href="#API"]').simulate('click');
@ -22,7 +22,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor>
<Link href="http://www.example.com/#API" title="API" />
</Anchor>
</Anchor>,
);
wrapper.find('a[href="http://www.example.com/#API"]').simulate('click');
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
@ -39,7 +39,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor>
<Link href="http://www.example.com/#API" title="API" />
</Anchor>
</Anchor>,
);
wrapper.instance().handleScroll();
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
@ -57,7 +57,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor>
<Link href="##API" title="API" />
</Anchor>
</Anchor>,
);
wrapper.instance().handleScrollTo('##API');
expect(wrapper.instance().state.activeLink).toBe('##API');
@ -70,7 +70,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>
</Anchor>,
);
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
wrapper.unmount();
@ -81,7 +81,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>
</Anchor>,
);
expect(wrapper.instance().links).toEqual(['#API']);
wrapper.setProps({ children: null });
@ -92,7 +92,11 @@ describe('Anchor Render', () => {
let anchorInstance = null;
function AnchorUpdate({ href }) {
return (
<Anchor ref={(c) => { anchorInstance = c; }}>
<Anchor
ref={c => {
anchorInstance = c;
}}
>
<Link href={href} title="API" />
</Anchor>
);
@ -107,7 +111,9 @@ describe('Anchor Render', () => {
it('Anchor onClick event', () => {
let event;
let link;
const handleClick = (...arg) => { [event, link] = arg; };
const handleClick = (...arg) => {
[event, link] = arg;
};
const href = '#API';
const title = 'API';
@ -115,7 +121,7 @@ describe('Anchor Render', () => {
const wrapper = mount(
<Anchor onClick={handleClick}>
<Link href={href} title={title} />
</Anchor>
</Anchor>,
);
wrapper.find(`a[href="${href}"]`).simulate('click');

8
components/anchor/style/index.less

@ -1,5 +1,5 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@anchor-border-width: 2px;
@ -38,7 +38,7 @@
border: 2px solid @primary-color;
background-color: @component-background;
left: 50%;
transition: top .3s ease-in-out;
transition: top 0.3s ease-in-out;
transform: translateX(-50%);
&.visible {
display: inline-block;
@ -57,7 +57,7 @@
&-title {
display: block;
position: relative;
transition: all .3s;
transition: all 0.3s;
color: @text-color;
white-space: nowrap;
overflow: hidden;

22
components/auto-complete/InputElement.tsx

@ -9,22 +9,28 @@ export default class InputElement extends React.Component<InputElementProps, any
private ele: HTMLInputElement;
focus = () => {
this.ele.focus ? this.ele.focus() : (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).focus();
}
this.ele.focus
? this.ele.focus()
: (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).focus();
};
blur = () => {
this.ele.blur ? this.ele.blur() : (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).blur();
}
};
saveRef = (ele: HTMLInputElement) => {
this.ele = ele;
const { ref: childRef } = this.props.children as any;
if (typeof childRef === 'function') {
childRef(ele);
}
}
};
render() {
return React.cloneElement(this.props.children, {
...this.props,
ref: this.saveRef,
}, null);
return React.cloneElement(
this.props.children,
{
...this.props,
ref: this.saveRef,
},
null,
);
}
}

11
components/auto-complete/__tests__/index.test.js

@ -10,12 +10,17 @@ describe('AutoComplete with Custom Input Element Render', () => {
const wrapper = mount(
<AutoComplete dataSource={['12345', '23456', '34567']}>
<textarea />
</AutoComplete>
</AutoComplete>,
);
expect(wrapper.find('textarea').length).toBe(1);
wrapper.find('textarea').simulate('change', { target: { value: '123' } });
const dropdownWrapper = mount(wrapper.find('Trigger').instance().getComponent());
const dropdownWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
// should not filter data source defaultly
expect(dropdownWrapper.find('MenuItem').length).toBe(3);
@ -26,7 +31,7 @@ describe('AutoComplete with Custom Input Element Render', () => {
mount(
<AutoComplete dataSource={[]}>
<input ref={mockRef} />
</AutoComplete>
</AutoComplete>,
);
expect(mockRef).toHaveBeenCalled();
});

92
components/auto-complete/index.tsx

@ -5,12 +5,15 @@ import Select, { AbstractSelectProps, SelectValue, OptionProps, OptGroupProps }
import Input from '../input';
import InputElement from './InputElement';
export interface DataSourceItemObject { value: string; text: string; }
export interface DataSourceItemObject {
value: string;
text: string;
}
export type DataSourceItemType =
string |
DataSourceItemObject |
React.ReactElement<OptionProps> |
React.ReactElement<OptGroupProps>;
| string
| DataSourceItemObject
| React.ReactElement<OptionProps>
| React.ReactElement<OptGroupProps>;
export interface AutoCompleteInputProps {
onChange?: React.FormEventHandler<any>;
@ -18,9 +21,9 @@ export interface AutoCompleteInputProps {
}
export type ValidInputElement =
HTMLInputElement |
HTMLTextAreaElement |
React.ReactElement<AutoCompleteInputProps>;
| HTMLInputElement
| HTMLTextAreaElement
| React.ReactElement<AutoCompleteInputProps>;
export interface AutoCompleteProps extends AbstractSelectProps {
value?: SelectValue;
@ -33,9 +36,10 @@ export interface AutoCompleteProps extends AbstractSelectProps {
onSelect?: (value: SelectValue, option: Object) => any;
onBlur?: (value: SelectValue) => void;
onFocus?: () => void;
children?: ValidInputElement |
React.ReactElement<OptionProps> |
Array<React.ReactElement<OptionProps>>;
children?:
| ValidInputElement
| React.ReactElement<OptionProps>
| Array<React.ReactElement<OptionProps>>;
}
function isSelectOptionOrSelectOptGroup(child: any): Boolean {
@ -59,15 +63,17 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, {}>
getInputElement = () => {
const { children } = this.props;
const element = children && React.isValidElement(children) && children.type !== Option ?
React.Children.only(this.props.children) : <Input />;
const element =
children && React.isValidElement(children) && children.type !== Option ? (
React.Children.only(this.props.children)
) : (
<Input />
);
const elementProps = { ...element.props };
// https://github.com/ant-design/ant-design/pull/7742
delete elementProps.children;
return (
<InputElement {...elementProps}>{element}</InputElement>
);
}
return <InputElement {...elementProps}>{element}</InputElement>;
};
focus() {
this.select.focus();
@ -79,11 +85,17 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, {}>
saveSelect = (node: any) => {
this.select = node;
}
};
render() {
const {
size, className = '', notFoundContent, prefixCls, optionLabelProp, dataSource, children,
size,
className = '',
notFoundContent,
prefixCls,
optionLabelProp,
dataSource,
children,
} = this.props;
const cls = classNames({
@ -96,28 +108,30 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, {}>
let options;
const childArray = React.Children.toArray(children);
if (childArray.length &&
isSelectOptionOrSelectOptGroup(childArray[0])
) {
if (childArray.length && isSelectOptionOrSelectOptGroup(childArray[0])) {
options = children;
} else {
options = dataSource ? dataSource.map((item) => {
if (React.isValidElement(item)) {
return item;
}
switch (typeof item) {
case 'string':
return <Option key={item}>{item}</Option>;
case 'object':
return (
<Option key={(item as DataSourceItemObject).value}>
{(item as DataSourceItemObject).text}
</Option>
);
default:
throw new Error('AutoComplete[dataSource] only supports type `string[] | Object[]`.');
}
}) : [];
options = dataSource
? dataSource.map(item => {
if (React.isValidElement(item)) {
return item;
}
switch (typeof item) {
case 'string':
return <Option key={item}>{item}</Option>;
case 'object':
return (
<Option key={(item as DataSourceItemObject).value}>
{(item as DataSourceItemObject).text}
</Option>
);
default:
throw new Error(
'AutoComplete[dataSource] only supports type `string[] | Object[]`.',
);
}
})
: [];
}
return (

12
components/auto-complete/style/index.less

@ -1,10 +1,10 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "../../input/style/mixin";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@input-prefix-cls: ~"@{ant-prefix}-input";
@select-prefix-cls: ~"@{ant-prefix}-select";
@autocomplete-prefix-cls: ~"@{select-prefix-cls}-auto-complete";
@input-prefix-cls: ~'@{ant-prefix}-input';
@select-prefix-cls: ~'@{ant-prefix}-select';
@autocomplete-prefix-cls: ~'@{select-prefix-cls}-auto-complete';
.@{autocomplete-prefix-cls} {
.reset-component;

4
components/avatar/__tests__/Avatar.test.js

@ -38,14 +38,14 @@ describe('Avatar Render', () => {
class Foo extends React.Component {
state = {
src: LOAD_FAILURE_SRC,
}
};
handleImgError = () => {
this.setState({
src: LOAD_SUCCESS_SRC,
});
return false;
}
};
render() {
const { src } = this.state;

61
components/avatar/index.tsx

@ -51,9 +51,11 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
}
componentDidUpdate(prevProps: AvatarProps, prevState: AvatarState) {
if (prevProps.children !== this.props.children
|| (prevState.scale !== this.state.scale && this.state.scale === 1)
|| (prevState.isImgExist !== this.state.isImgExist)) {
if (
prevProps.children !== this.props.children ||
(prevState.scale !== this.state.scale && this.state.scale === 1) ||
prevState.isImgExist !== this.state.isImgExist
) {
this.setScale();
}
}
@ -75,7 +77,7 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
});
}
}
}
};
handleImgLoadError = () => {
const { onError } = this.props;
@ -83,12 +85,10 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
if (errorFlag !== false) {
this.setState({ isImgExist: false });
}
}
};
render() {
const {
prefixCls, shape, size, src, srcSet, icon, className, alt, ...others
} = this.props;
const { prefixCls, shape, size, src, srcSet, icon, className, alt, ...others } = this.props;
const { isImgExist, scale } = this.state;
@ -103,23 +103,19 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
[`${prefixCls}-icon`]: icon,
});
const sizeStyle: React.CSSProperties = typeof size === 'number' ? {
width: size,
height: size,
lineHeight: `${size}px`,
fontSize: icon ? size / 2 : 18,
} : {};
const sizeStyle: React.CSSProperties =
typeof size === 'number'
? {
width: size,
height: size,
lineHeight: `${size}px`,
fontSize: icon ? size / 2 : 18,
}
: {};
let children = this.props.children;
if (src && isImgExist) {
children = (
<img
src={src}
srcSet={srcSet}
onError={this.handleImgLoadError}
alt={alt}
/>
);
children = <img src={src} srcSet={srcSet} onError={this.handleImgLoadError} alt={alt} />;
} else if (icon) {
children = <Icon type={icon} />;
} else {
@ -132,13 +128,15 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
transform: transformString,
};
const sizeChildrenStyle: React.CSSProperties =
typeof size === 'number' ? {
lineHeight: `${size}px`,
} : {};
typeof size === 'number'
? {
lineHeight: `${size}px`,
}
: {};
children = (
<span
className={`${prefixCls}-string`}
ref={span => this.avatarChildren = span}
ref={span => (this.avatarChildren = span)}
style={{ ...sizeChildrenStyle, ...childrenStyle }}
>
{children}
@ -146,21 +144,14 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
);
} else {
children = (
<span
className={`${prefixCls}-string`}
ref={span => this.avatarChildren = span}
>
<span className={`${prefixCls}-string`} ref={span => (this.avatarChildren = span)}>
{children}
</span>
);
}
}
return (
<span
{...others}
style={{ ...sizeStyle, ...others.style }}
className={classString}
>
<span {...others} style={{ ...sizeStyle, ...others.style }} className={classString}>
{children}
</span>
);

6
components/avatar/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@avatar-prefix-cls: ~"@{ant-prefix}-avatar";
@avatar-prefix-cls: ~'@{ant-prefix}-avatar';
.@{avatar-prefix-cls} {
.reset-component;

12
components/back-top/index.tsx

@ -10,13 +10,13 @@ const easeInOutCubic = (t: number, b: number, c: number, d: number) => {
const cc = c - b;
t /= d / 2;
if (t < 1) {
return cc / 2 * t * t * t + b;
return (cc / 2) * t * t * t + b;
} else {
return cc / 2 * ((t -= 2) * t * t + 2) + b;
return (cc / 2) * ((t -= 2) * t * t + 2) + b;
}
};
function noop() { }
function noop() {}
function getDefaultTarget() {
return window;
@ -52,7 +52,7 @@ export default class BackTop extends React.Component<BackTopProps, any> {
return window.pageYOffset || document.body.scrollTop || document.documentElement!.scrollTop;
}
return (targetNode as HTMLElement).scrollTop;
}
};
scrollToTop = (e: React.MouseEvent<HTMLDivElement>) => {
const scrollTop = this.getCurrentScrollTop();
@ -69,7 +69,7 @@ export default class BackTop extends React.Component<BackTopProps, any> {
};
raf(frameFunc);
(this.props.onClick || noop)(e);
}
};
setScrollTop(value: number) {
const getTarget = this.props.target || getDefaultTarget;
@ -88,7 +88,7 @@ export default class BackTop extends React.Component<BackTopProps, any> {
this.setState({
visible: scrollTop > (visibilityHeight as number),
});
}
};
componentDidMount() {
const getTarget = this.props.target || getDefaultTarget;

13
components/back-top/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@backtop-prefix-cls: ~"@{ant-prefix}-back-top";
@backtop-prefix-cls: ~'@{ant-prefix}-back-top';
.@{backtop-prefix-cls} {
.reset-component;
@ -20,12 +20,12 @@
background-color: @back-top-bg;
color: @back-top-color;
text-align: center;
transition: all .3s @ease-in-out;
transition: all 0.3s @ease-in-out;
overflow: hidden;
&:hover {
background-color: @back-top-hover-bg;
transition: all .3s @ease-in-out;
transition: all 0.3s @ease-in-out;
}
}
@ -33,7 +33,8 @@
margin: 12px auto;
width: 14px;
height: 16px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) ~"100%/100%" no-repeat;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC)
~'100%/100%' no-repeat;
}
}

98
components/badge/ScrollNumber.tsx

@ -4,11 +4,13 @@ import omit from 'omit.js';
import classNames from 'classnames';
function getNumberArray(num: string | number | undefined | null) {
return num ?
num.toString()
.split('')
.reverse()
.map(i => Number(i)) : [];
return num
? num
.toString()
.split('')
.reverse()
.map(i => Number(i))
: [];
}
export interface ScrollNumberProps {
@ -31,8 +33,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
static defaultProps = {
prefixCls: 'ant-scroll-number',
count: null,
onAnimated() {
},
onAnimated() {},
};
lastCount: any;
@ -71,49 +72,63 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
}
this.lastCount = this.state.count;
// 复原数字初始位置
this.setState({
animateStarted: true,
}, () => {
// 等待数字位置复原完毕
// 开始设置完整的数字
setTimeout(() => {
this.setState({
animateStarted: false,
count: nextProps.count,
}, () => {
const onAnimated = this.props.onAnimated;
if (onAnimated) {
onAnimated();
}
});
}, 5);
});
this.setState(
{
animateStarted: true,
},
() => {
// 等待数字位置复原完毕
// 开始设置完整的数字
setTimeout(() => {
this.setState(
{
animateStarted: false,
count: nextProps.count,
},
() => {
const onAnimated = this.props.onAnimated;
if (onAnimated) {
onAnimated();
}
},
);
}, 5);
},
);
}
}
renderNumberList(position: number) {
const childrenToReturn: React.ReactElement<any>[] = [];
for (let i = 0; i < 30; i++) {
const currentClassName = (position === i) ? 'current' : '';
childrenToReturn.push(<p key={i.toString()} className={currentClassName}>{i % 10}</p>);
const currentClassName = position === i ? 'current' : '';
childrenToReturn.push(
<p key={i.toString()} className={currentClassName}>
{i % 10}
</p>,
);
}
return childrenToReturn;
}
renderCurrentNumber(num: number, i: number) {
const position = this.getPositionByNum(num, i);
const removeTransition = this.state.animateStarted ||
(getNumberArray(this.lastCount)[i] === undefined);
return createElement('span', {
className: `${this.props.prefixCls}-only`,
style: {
transition: removeTransition ? 'none' : undefined,
msTransform: `translateY(${-position * 100}%)`,
WebkitTransform: `translateY(${-position * 100}%)`,
transform: `translateY(${-position * 100}%)`,
const removeTransition =
this.state.animateStarted || getNumberArray(this.lastCount)[i] === undefined;
return createElement(
'span',
{
className: `${this.props.prefixCls}-only`,
style: {
transition: removeTransition ? 'none' : undefined,
msTransform: `translateY(${-position * 100}%)`,
WebkitTransform: `translateY(${-position * 100}%)`,
transform: `translateY(${-position * 100}%)`,
},
key: i,
},
key: i,
}, this.renderNumberList(position));
this.renderNumberList(position),
);
}
renderNumberElement() {
@ -122,7 +137,8 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
return count;
}
return getNumberArray(count)
.map((num, i) => this.renderCurrentNumber(num, i)).reverse();
.map((num, i) => this.renderCurrentNumber(num, i))
.reverse();
}
render() {
@ -151,10 +167,6 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
className: `${prefixCls}-custom-component`,
});
}
return createElement(
component as any,
newProps,
this.renderNumberElement(),
);
return createElement(component as any, newProps, this.renderNumberElement());
}
}

16
components/badge/__tests__/index.test.js

@ -24,7 +24,12 @@ describe('Badge', () => {
it('should have an overriden title attribute', () => {
const badge = mount(<Badge count={10} title="Custom title" />);
expect(badge.find('.ant-scroll-number').getDOMNode().attributes.getNamedItem('title').value).toEqual('Custom title');
expect(
badge
.find('.ant-scroll-number')
.getDOMNode()
.attributes.getNamedItem('title').value,
).toEqual('Custom title');
});
// https://github.com/ant-design/ant-design/issues/10626
@ -32,7 +37,7 @@ describe('Badge', () => {
const wrapper = mount(
<Tooltip title="Fix the error">
<Badge status="error" />
</Tooltip>
</Tooltip>,
);
wrapper.find('Badge').simulate('mouseenter');
jest.runAllTimers();
@ -59,7 +64,12 @@ describe('Badge', () => {
});
it('should be compatible with borderColor style', () => {
const wrapper = render(<Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} />);
const wrapper = render(
<Badge
count={4}
style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }}
/>,
);
expect(wrapper).toMatchSnapshot();
});
});

40
components/badge/index.tsx

@ -42,12 +42,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
};
getBadgeClassName() {
const {
prefixCls,
className,
status,
children,
} = this.props;
const { prefixCls, className, status, children } = this.props;
return classNames(className, prefixCls, {
[`${prefixCls}-status`]: !!status,
[`${prefixCls}-not-a-wrapper`]: !children,
@ -76,7 +71,8 @@ export default class Badge extends React.Component<BadgeProps, any> {
getNumberedDispayCount() {
const { count, overflowCount } = this.props;
const displayCount = (count as number) > (overflowCount as number) ? `${overflowCount}+` : count;
const displayCount =
(count as number) > (overflowCount as number) ? `${overflowCount}+` : count;
return displayCount as string | number | null;
}
@ -94,38 +90,33 @@ export default class Badge extends React.Component<BadgeProps, any> {
if (title) {
return title;
}
return (typeof count === 'string' || typeof count === 'number') ? count : undefined;
return typeof count === 'string' || typeof count === 'number' ? count : undefined;
}
getStyleWithOffset() {
const { offset, style } = this.props;
return offset ? {
right: -parseInt(offset[0] as string, 10),
marginTop: offset[1],
...style,
} : style;
return offset
? {
right: -parseInt(offset[0] as string, 10),
marginTop: offset[1],
...style,
}
: style;
}
renderStatusText() {
const { prefixCls, text } = this.props;
const hidden = this.isHidden();
return (hidden || !text) ? null : (
<span className={`${prefixCls}-status-text`}>{text}</span>
);
return hidden || !text ? null : <span className={`${prefixCls}-status-text`}>{text}</span>;
}
renderDispayComponent() {
const { count } = this.props;
return (count && typeof count === 'object') ? (count as React.ReactElement<any>) : undefined;
return count && typeof count === 'object' ? (count as React.ReactElement<any>) : undefined;
}
renderBadgeNumber() {
const {
count,
prefixCls,
scrollNumberPrefixCls,
status,
} = this.props;
const { count, prefixCls, scrollNumberPrefixCls, status } = this.props;
const displayCount = this.getDispayCount();
const isDot = this.isDot();
@ -134,7 +125,8 @@ export default class Badge extends React.Component<BadgeProps, any> {
const scrollNumberCls = classNames({
[`${prefixCls}-dot`]: isDot,
[`${prefixCls}-count`]: !isDot,
[`${prefixCls}-multiple-words`]: !isDot && count && count.toString && count.toString().length > 1,
[`${prefixCls}-multiple-words`]:
!isDot && count && count.toString && count.toString().length > 1,
[`${prefixCls}-status-${status}`]: !!status,
});

16
components/badge/style/index.less

@ -1,8 +1,8 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@badge-prefix-cls: ~"@{ant-prefix}-badge";
@number-prefix-cls: ~"@{ant-prefix}-scroll-number";
@badge-prefix-cls: ~'@{ant-prefix}-badge';
@number-prefix-cls: ~'@{ant-prefix}-scroll-number';
.@{badge-prefix-cls} {
.reset-component;
@ -86,7 +86,7 @@
height: 100%;
border-radius: 50%;
border: 1px solid @processing-color;
content: "";
content: '';
animation: antStatusProcessing 1.2s infinite ease-in-out;
}
}
@ -108,12 +108,12 @@
&-zoom-appear,
&-zoom-enter {
animation: antZoomBadgeIn .3s @ease-out-back;
animation: antZoomBadgeIn 0.3s @ease-out-back;
animation-fill-mode: both;
}
&-zoom-leave {
animation: antZoomBadgeOut .3s @ease-in-back;
animation: antZoomBadgeOut 0.3s @ease-in-back;
animation-fill-mode: both;
}
@ -147,7 +147,7 @@
overflow: hidden;
&-only {
display: inline-block;
transition: all .3s @ease-in-out;
transition: all 0.3s @ease-in-out;
height: @badge-height;
> p {
height: @badge-height;

27
components/breadcrumb/Breadcrumb.tsx

@ -15,7 +15,12 @@ export interface BreadcrumbProps {
routes?: Route[];
params?: any;
separator?: React.ReactNode;
itemRender?: (route: any, params: any, routes: Array<any>, paths: Array<string>) => React.ReactNode;
itemRender?: (
route: any,
params: any,
routes: Array<any>,
paths: Array<string>,
) => React.ReactNode;
style?: React.CSSProperties;
className?: string;
}
@ -35,9 +40,7 @@ function getBreadcrumbName(route: Route, params: any) {
function defaultItemRender(route: Route, params: any, routes: Route[], paths: string[]) {
const isLastItem = routes.indexOf(route) === routes.length - 1;
const name = getBreadcrumbName(route, params);
return isLastItem
? <span>{name}</span>
: <a href={`#/${paths.join('/')}`}>{name}</a>;
return isLastItem ? <span>{name}</span> : <a href={`#/${paths.join('/')}`}>{name}</a>;
}
export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
@ -62,19 +65,25 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
warning(
!('linkRender' in props || 'nameRender' in props),
'`linkRender` and `nameRender` are removed, please use `itemRender` instead, ' +
'see: https://u.ant.design/item-render.',
'see: https://u.ant.design/item-render.',
);
}
render() {
let crumbs;
const {
separator, prefixCls, style, className, routes, params = {},
children, itemRender = defaultItemRender,
separator,
prefixCls,
style,
className,
routes,
params = {},
children,
itemRender = defaultItemRender,
} = this.props;
if (routes && routes.length > 0) {
const paths: string[] = [];
crumbs = routes.map((route) => {
crumbs = routes.map(route => {
route.path = route.path || '';
let path: string = route.path.replace(/^\//, '');
Object.keys(params).forEach(key => {
@ -96,7 +105,7 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
}
warning(
element.type && element.type.__ANT_BREADCRUMB_ITEM,
'Breadcrumb only accepts Breadcrumb.Item as it\'s children',
"Breadcrumb only accepts Breadcrumb.Item as it's children",
);
return cloneElement(element, {
separator,

17
components/breadcrumb/BreadcrumbItem.tsx

@ -17,10 +17,7 @@ export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps,
static propTypes = {
prefixCls: PropTypes.string,
separator: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]),
separator: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
href: PropTypes.string,
};
@ -28,9 +25,17 @@ export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps,
const { prefixCls, separator, children, ...restProps } = this.props;
let link;
if ('href' in this.props) {
link = <a className={`${prefixCls}-link`} {...restProps}>{children}</a>;
link = (
<a className={`${prefixCls}-link`} {...restProps}>
{children}
</a>
);
} else {
link = <span className={`${prefixCls}-link`} {...restProps}>{children}</span>;
link = (
<span className={`${prefixCls}-link`} {...restProps}>
{children}
</span>
);
}
if (children) {
return (

8
components/breadcrumb/__tests__/Breadcrumb.test.js

@ -19,11 +19,11 @@ describe('Breadcrumb', () => {
mount(
<Breadcrumb>
<MyCom />
</Breadcrumb>
</Breadcrumb>,
);
expect(errorSpy.mock.calls).toHaveLength(1);
expect(errorSpy.mock.calls[0][0]).toMatch(
'Breadcrumb only accepts Breadcrumb.Item as it\'s children'
"Breadcrumb only accepts Breadcrumb.Item as it's children",
);
});
@ -34,7 +34,7 @@ describe('Breadcrumb', () => {
{null}
<Breadcrumb.Item>Home</Breadcrumb.Item>
{undefined}
</Breadcrumb>
</Breadcrumb>,
);
expect(errorSpy).not.toHaveBeenCalled();
expect(wrapper).toMatchSnapshot();
@ -47,7 +47,7 @@ describe('Breadcrumb', () => {
<Breadcrumb.Item />
<Breadcrumb.Item>xxx</Breadcrumb.Item>
<Breadcrumb.Item>yyy</Breadcrumb.Item>
</Breadcrumb>
</Breadcrumb>,
);
expect(wrapper).toMatchSnapshot();
});

171
components/breadcrumb/__tests__/router.test.js

@ -1,7 +1,5 @@
import React from 'react';
import {
Route, Switch, Link, withRouter, MemoryRouter,
} from 'react-router-dom';
import { Route, Switch, Link, withRouter, MemoryRouter } from 'react-router-dom';
import { mount } from 'enzyme';
import Breadcrumb from '../index';
@ -24,24 +22,22 @@ const breadcrumbNameMap = {
'/apps/2/detail': 'Detail',
};
const Home = withRouter((props) => {
const Home = withRouter(props => {
const { location, history } = props;
const pathSnippets = location.pathname.split('/').filter(i => i);
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
return (
<Breadcrumb.Item key={url}>
<Link to={url}>
{breadcrumbNameMap[url]}
</Link>
<Link to={url}>{breadcrumbNameMap[url]}</Link>
</Breadcrumb.Item>
);
});
const breadcrumbItems = [(
const breadcrumbItems = [
<Breadcrumb.Item key="home">
<Link to="/">Home</Link>
</Breadcrumb.Item>
)].concat(extraBreadcrumbItems);
</Breadcrumb.Item>,
].concat(extraBreadcrumbItems);
return (
<div className="demo">
<div className="demo-nav">
@ -52,9 +48,7 @@ const Home = withRouter((props) => {
<Route path="/apps" component={Apps} />
<Route render={() => <span>Home Page</span>} />
</Switch>
<Breadcrumb>
{breadcrumbItems}
</Breadcrumb>
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
</div>
);
});
@ -72,81 +66,94 @@ describe('react router', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']} initialIndex={0}>
<Home />
</MemoryRouter>
</MemoryRouter>,
);
expect(wrapper.find('BreadcrumbItem').length).toBe(1);
expect(wrapper.find('BreadcrumbItem .ant-breadcrumb-link').at(0).text()).toBe('Home');
wrapper.find('.demo-nav a').at(1).simulate('click');
expect(
wrapper
.find('BreadcrumbItem .ant-breadcrumb-link')
.at(0)
.text(),
).toBe('Home');
wrapper
.find('.demo-nav a')
.at(1)
.simulate('click');
expect(wrapper.find('BreadcrumbItem').length).toBe(2);
expect(wrapper.find('BreadcrumbItem .ant-breadcrumb-link').at(1).text()).toBe('Application List');
expect(
wrapper
.find('BreadcrumbItem .ant-breadcrumb-link')
.at(1)
.text(),
).toBe('Application List');
});
it('react router 3', () => {
const routes = [{
name: 'home',
breadcrumbName: 'Home',
path: '/',
childRoutes: [
{
name: 'apps',
breadcrumbName: 'Application List',
path: 'apps',
childRoutes: [
{
name: 'app',
breadcrumbName: 'Application:id',
path: ':id',
childRoutes: [
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
],
},
],
},
],
},
{
name: 'apps',
breadcrumbName: 'Application List',
path: 'apps',
childRoutes: [
{
name: 'app',
breadcrumbName: 'Application:id',
path: ':id',
childRoutes: [
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
],
},
],
},
{
name: 'app',
breadcrumbName: 'Application:id',
path: ':id',
childRoutes: [
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
],
},
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
}];
const wrapper = mount(
<Breadcrumb routes={routes} params={{ id: 1 }} />
);
const routes = [
{
name: 'home',
breadcrumbName: 'Home',
path: '/',
childRoutes: [
{
name: 'apps',
breadcrumbName: 'Application List',
path: 'apps',
childRoutes: [
{
name: 'app',
breadcrumbName: 'Application:id',
path: ':id',
childRoutes: [
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
],
},
],
},
],
},
{
name: 'apps',
breadcrumbName: 'Application List',
path: 'apps',
childRoutes: [
{
name: 'app',
breadcrumbName: 'Application:id',
path: ':id',
childRoutes: [
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
],
},
],
},
{
name: 'app',
breadcrumbName: 'Application:id',
path: ':id',
childRoutes: [
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
],
},
{
name: 'detail',
breadcrumbName: 'Detail',
path: 'detail',
},
];
const wrapper = mount(<Breadcrumb routes={routes} params={{ id: 1 }} />);
expect(wrapper).toMatchSnapshot();
});
});

8
components/breadcrumb/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@breadcrumb-prefix-cls: ~"@{ant-prefix}-breadcrumb";
@breadcrumb-prefix-cls: ~'@{ant-prefix}-breadcrumb';
.@{breadcrumb-prefix-cls} {
.reset-component;
@ -14,7 +14,7 @@
a {
color: @breadcrumb-link-color;
transition: color .3s;
transition: color 0.3s;
&:hover {
color: @breadcrumb-link-color-hover;
}

90
components/button/__tests__/index.test.js

@ -6,9 +6,7 @@ import Icon from '../../icon';
describe('Button', () => {
it('renders correctly', () => {
const wrapper = render(
<Button>Follow</Button>
);
const wrapper = render(<Button>Follow</Button>);
expect(wrapper).toMatchSnapshot();
});
@ -20,41 +18,40 @@ describe('Button', () => {
});
it('renders Chinese characters correctly', () => {
const wrapper = render(
<Button>按钮</Button>
);
const wrapper = render(<Button>按钮</Button>);
expect(wrapper).toMatchSnapshot();
// should not insert space when there is icon
const wrapper1 = render(
<Button icon="search">按钮</Button>
);
const wrapper1 = render(<Button icon="search">按钮</Button>);
expect(wrapper1).toMatchSnapshot();
// should not insert space when there is icon
const wrapper2 = render(
<Button><Icon type="search" />按钮</Button>
<Button>
<Icon type="search" />
按钮
</Button>,
);
expect(wrapper2).toMatchSnapshot();
// should not insert space when there is icon
const wrapper3 = render(
<Button icon="search">按钮</Button>
);
const wrapper3 = render(<Button icon="search">按钮</Button>);
expect(wrapper3).toMatchSnapshot();
// should not insert space when there is icon while loading
const wrapper4 = render(
<Button icon="search" loading>按钮</Button>
<Button icon="search" loading>
按钮
</Button>,
);
expect(wrapper4).toMatchSnapshot();
// should insert space while loading
const wrapper5 = render(
<Button loading>按钮</Button>
);
const wrapper5 = render(<Button loading>按钮</Button>);
expect(wrapper5).toMatchSnapshot();
});
it('renders Chinese characters correctly in HOC', () => {
const Text = ({ children }) => <span>{children}</span>;
const wrapper = mount(
<Button><Text>按钮</Text></Button>
<Button>
<Text>按钮</Text>
</Button>,
);
expect(wrapper.find('.ant-btn').hasClass('ant-btn-two-chinese-chars')).toBe(true);
wrapper.setProps({
@ -70,9 +67,7 @@ describe('Button', () => {
});
it('have static property for type detecting', () => {
const wrapper = mount(
<Button>Button Text</Button>
);
const wrapper = mount(<Button>Button Text</Button>);
// eslint-disable-next-line
expect(wrapper.type().__ANT_BUTTON).toBe(true);
});
@ -85,16 +80,18 @@ describe('Button', () => {
enterLoading = () => {
this.setState({ loading: true });
}
};
render() {
const { loading } = this.state;
return <Button loading={loading} onClick={this.enterLoading}>Button</Button>;
return (
<Button loading={loading} onClick={this.enterLoading}>
Button
</Button>
);
}
}
const wrapper = mount(
<DefaultButton />
);
const wrapper = mount(<DefaultButton />);
wrapper.simulate('click');
expect(wrapper.find('.ant-btn-loading').length).toBe(1);
});
@ -108,54 +105,55 @@ describe('Button', () => {
enterLoading = () => {
this.setState({ loading: { delay: 1000 } });
}
};
render() {
const { loading } = this.state;
return <Button loading={loading} onClick={this.enterLoading}>Button</Button>;
return (
<Button loading={loading} onClick={this.enterLoading}>
Button
</Button>
);
}
}
const wrapper = mount(
<DefaultButton />
);
const wrapper = mount(<DefaultButton />);
wrapper.simulate('click');
expect(wrapper.hasClass('ant-btn-loading')).toBe(false);
});
it('should support link button', () => {
const wrapper = mount(
<Button target="_blank" href="http://ant.design">link button</Button>
<Button target="_blank" href="http://ant.design">
link button
</Button>,
);
expect(wrapper.render()).toMatchSnapshot();
});
it('fixbug renders {0} , 0 and {false}', () => {
const wrapper = render(
<Button>{0}</Button>
);
const wrapper = render(<Button>{0}</Button>);
expect(wrapper).toMatchSnapshot();
const wrapper1 = render(
<Button>0</Button>
);
const wrapper1 = render(<Button>0</Button>);
expect(wrapper1).toMatchSnapshot();
const wrapper2 = render(
<Button>{false}</Button>
);
const wrapper2 = render(<Button>{false}</Button>);
expect(wrapper2).toMatchSnapshot();
});
it('should has click wave effect', async () => {
const wrapper = mount(
<Button type="primary">button</Button>
);
wrapper.find('.ant-btn').getDOMNode().click();
const wrapper = mount(<Button type="primary">button</Button>);
wrapper
.find('.ant-btn')
.getDOMNode()
.click();
await new Promise(resolve => setTimeout(resolve, 0));
expect(wrapper.render()).toMatchSnapshot();
});
it('should not render as link button when href is undefined', async () => {
const wrapper = mount(
<Button type="primary" href={undefined}>button</Button>
<Button type="primary" href={undefined}>
button
</Button>,
);
expect(wrapper.render()).toMatchSnapshot();
});

12
components/button/button-group.tsx

@ -9,7 +9,7 @@ export interface ButtonGroupProps {
prefixCls?: string;
}
const ButtonGroup: React.SFC<ButtonGroupProps> = (props) => {
const ButtonGroup: React.SFC<ButtonGroupProps> = props => {
const { prefixCls = 'ant-btn-group', size, className, ...others } = props;
// large => lg
@ -25,9 +25,13 @@ const ButtonGroup: React.SFC<ButtonGroupProps> = (props) => {
break;
}
const classes = classNames(prefixCls, {
[`${prefixCls}-${sizeCls}`]: sizeCls,
}, className);
const classes = classNames(
prefixCls,
{
[`${prefixCls}-${sizeCls}`]: sizeCls,
},
className,
);
return <div {...others} className={classes} />;
};

49
components/button/button.tsx

@ -19,10 +19,13 @@ function insertSpace(child: React.ReactChild, needInserted: boolean) {
}
const SPACE = needInserted ? ' ' : '';
// strictNullChecks oops.
if (typeof child !== 'string' && typeof child !== 'number' &&
isString(child.type) && isTwoCNChar(child.props.children)) {
return React.cloneElement(child, {},
child.props.children.split('').join(SPACE));
if (
typeof child !== 'string' &&
typeof child !== 'number' &&
isString(child.type) &&
isTwoCNChar(child.props.children)
) {
return React.cloneElement(child, {}, child.props.children.split('').join(SPACE));
}
if (typeof child === 'string') {
if (isTwoCNChar(child)) {
@ -55,12 +58,14 @@ export type AnchorButtonProps = {
href: string;
target?: string;
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
} & BaseButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>;
} & BaseButtonProps &
React.AnchorHTMLAttributes<HTMLAnchorElement>;
export type NativeButtonProps = {
htmlType?: ButtonHTMLType;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
} & BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>;
} & BaseButtonProps &
React.ButtonHTMLAttributes<HTMLButtonElement>;
export type ButtonProps = AnchorButtonProps | NativeButtonProps;
@ -129,7 +134,7 @@ export default class Button extends React.Component<ButtonProps, any> {
saveButtonRef = (node: HTMLElement | null) => {
this.buttonNode = node;
}
};
fixTwoCNChar() {
// Fix for HOC usage like <FormatMessage />
@ -159,7 +164,7 @@ export default class Button extends React.Component<ButtonProps, any> {
if (onClick) {
(onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)(e);
}
}
};
isNeedInserted() {
const { icon, children } = this.props;
@ -168,7 +173,17 @@ export default class Button extends React.Component<ButtonProps, any> {
render() {
const {
type, shape, size, className, children, icon, prefixCls, ghost, loading: _loadingProp, block, ...rest
type,
shape,
size,
className,
children,
icon,
prefixCls,
ghost,
loading: _loadingProp,
block,
...rest
} = this.props;
const { loading, hasTwoCNChar } = this.state;
@ -202,10 +217,12 @@ export default class Button extends React.Component<ButtonProps, any> {
const iconType = loading ? 'loading' : icon;
const iconNode = iconType ? <Icon type={iconType} /> : null;
const kids = (children || children === 0)
? React.Children.map(children, child => insertSpace(child, this.isNeedInserted())) : null;
const kids =
children || children === 0
? React.Children.map(children, child => insertSpace(child, this.isNeedInserted()))
: null;
const title= isChristmas ? 'Ho Ho Ho!' : rest.title;
const title = isChristmas ? 'Ho Ho Ho!' : rest.title;
const linkButtonRestProps = rest as AnchorButtonProps;
if (linkButtonRestProps.href !== undefined) {
@ -217,7 +234,8 @@ export default class Button extends React.Component<ButtonProps, any> {
title={title}
ref={this.saveButtonRef}
>
{iconNode}{kids}
{iconNode}
{kids}
</a>
);
}
@ -228,14 +246,15 @@ export default class Button extends React.Component<ButtonProps, any> {
return (
<Wave>
<button
{...(otherProps as NativeButtonProps)}
{...otherProps as NativeButtonProps}
type={htmlType || 'button'}
className={classes}
onClick={this.handleClick}
title={title}
ref={this.saveButtonRef}
>
{iconNode}{kids}
{iconNode}
{kids}
</button>
</Wave>
);

25
components/button/style/index.less

@ -1,8 +1,8 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "./mixin";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@import './mixin';
@btn-prefix-cls: ~"@{ant-prefix}-btn";
@btn-prefix-cls: ~'@{ant-prefix}-btn';
// for compatible
@btn-ghost-color: @text-color;
@ -82,16 +82,16 @@
right: -1px;
background: #fff;
opacity: 0.35;
content: "";
content: '';
border-radius: inherit;
z-index: 1;
transition: opacity .2s;
transition: opacity 0.2s;
pointer-events: none;
display: none;
}
.@{iconfont-css-prefix} {
transition: margin-left .3s @ease-in-out;
transition: margin-left 0.3s @ease-in-out;
}
&&-loading:before {
@ -150,12 +150,12 @@
}
&-two-chinese-chars:first-letter {
letter-spacing: .34em;
letter-spacing: 0.34em;
}
&-two-chinese-chars > *:not(.@{iconfont-css-prefix}) {
letter-spacing: .34em;
margin-right: -.34em;
letter-spacing: 0.34em;
margin-right: -0.34em;
}
&-block {
@ -163,13 +163,14 @@
}
.christmas&-primary:before {
content: "";
content: '';
display: block;
position: absolute;
top: -6px;
left: 0;
right: 0;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAAXCAYAAABOHMIhAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABiZJREFUeNrsWMtPlFcUvzPMwIDysLyRR4uATDHWCiVgSmRlios2DeiiXUFs0nRBd6arxqQhJDapkYXhP4BqDKTQhZaFNQSCaBEVJjwdHsNr5DUMDDPDzPT3u7nTDEgRKrKgc5KT+z3uufec33de99P4fD4RpL2RNgjB3kn35MkTeRERESFiYmLkGBoaKnQ6nWSNRvPPZFxr+vv7k6KioiIdDsfa8vLyQkFBgcP3Bnel3MDAQArWI0eFhISE87nb7bZ7PJ4VvLYuLi5O5+fnu9+kMNfq6+tLjIyMzMY6KeBEbK/XarXReI3lPDZMWcc4v7GxYV1dXR3Jy8ub2E5HPvJ6vRSSDH0ku1wuAfsEZOV1IEFHoeNFdHS0yMrK2knR0Lm5uR+hxLdQMjbwHTZbB41h8RGwCdc9MzMzneHh4bGJiYlf4SN8ijkfwqiIncCAAR7Iz2GPSShudjqdfeCeqampvwBQfFxc3JdYqwTv8gB8/F48A8BgKecE14V+L7ju2tpae05OzkuCCZvkPOj8mizmC6vVKtmPu+bx48cC3qI1mUyFUOyywWD4SHlELBaLJmCHNcwAghuAOujtuF4FqHO4nsX4EsAS3I4TJ04ME1h8PDE9PS09TYZoY2Pj1729vd6lpSVfkDYTPG0UkfNDRUWFgQ5Gb2Mh0N29e9eG/GQfHh4W8/PzwUy/ObQ/gMfVVlZW1iAiZdQxp3nv3LljRoL/5erVq1UIxzSiiVD9X4EDYATynCwAzGO858hCQRoaGmJFZNJz8YIcBc4BF966dau6sLAwBxVSJCUlCSThQwuU3W6XkYUok1Vzm5znQx5bbm9v77p+/frPeNSNRzZ/ISBwrG4ZR48eLamtrf2+uLjYSEG9Xi/wTISFhQlWGXohyzO/CJlVl23KQRLbABoaHx+/Z1lUZ/Hq1SsJFj3JT3hmHx8fnydPTEzMj46OziHPW2w22wxeD4Kfgadh/4YEzU8Az4DhffAn5eXlX1y6dKkEoCTspAQ9Mjs7+0BBo8Fms1lkZGTsOo0QLLRNkvnR+fEJzIMHD0xtbW39CL8JTFtSbAOvBIyLHIGVm9VzE2gKuDAMSSpcT6KXyT137lx2cnLyMXhcGDb3wq3XuWF3d/fCzZs3P0c4v5eSknJQbYLo7Ox0gC2lpaVZ3Be67Th/dnZWoAJKsJC3XA8fPhxoamp6hMb+BaaMgWcUMGtszZjiFDNmvcDI91pzG0iY4ARwkwrxkcHBwUdgNrRMbnrqoRbkVzDcvn3bl5qaWsmcgFH4G8XdEGUWFhak51AuISFBnkoCTyFbyWKxCJwIxlC0fq2rq7tcVFRkRKskjh8/Lr0+kBjCCDV/knfdv3//WX19/R8IRRNemxlu4AXwKqM+EJwdj1HbPYSwh3sCPAJDABm2LLchCjS+5/kirKGhwWk0GrMuXrxYQuX9hm/XXTMXMY+srKwI5ApZrbYmZh7deEJhAUKjLe/pLTzSsCuHrK+1tbUJVe3P6upq87Vr174rKysrYHVj/uW+OH3IfEuw4F3ee/fuPQfAvwOs5yyE4CnlFOu7BWrTCWlreO6FACpBZGwUw4BvkANLobReHb3kGZYGsGzTq/zlO8AT1ru6uoZbWlqeA6gINJAfnz59OlVLoX8Jtebm5raampqfcMvQYgTknz9//sKVK1c+y83NTdIEuCnaKMuNGzd+6+np6cCtSTkAw9D9X8Dyh+dbgaaAC1XAnUlPTy+qqqq6cPbs2UzkmWjNljiDJzpwHFnCkW2yo6NjCKW8H54wjlezKvRT09LSTsJrz5w6dSoN+Yp51ADAPUj8VoDbDq9pxrwuJcNIYQllJTIi/xopBw/VA7DJp0+f9hA78CgL5F5C8J2CpoCj8sfA6WCe/FPRhsRlZmbGIs8Y4FFO5CJgtrSsvrRVGW1V93b1myoGnKAKEcHgnwsWpg1lNI0fphwrmdqbckeU18WrnlOjqp5/j7W3BWvfQVPKa5SBkcrYCNVB65TRTlWZ1lXiXVU5xbtlDb2SPaLWYwrgHIcqPg6Vc7fbX69Yoyqfa7/AeiegbWOEVhmsVcWDwPn224iDJgla8Hd38Hd3ELQgaIeI/hZgAIPEp0vmQJdoAAAAAElFTkSuQmCC) no-repeat 50% 0;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAAXCAYAAABOHMIhAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABiZJREFUeNrsWMtPlFcUvzPMwIDysLyRR4uATDHWCiVgSmRlios2DeiiXUFs0nRBd6arxqQhJDapkYXhP4BqDKTQhZaFNQSCaBEVJjwdHsNr5DUMDDPDzPT3u7nTDEgRKrKgc5KT+z3uufec33de99P4fD4RpL2RNgjB3kn35MkTeRERESFiYmLkGBoaKnQ6nWSNRvPPZFxr+vv7k6KioiIdDsfa8vLyQkFBgcP3Bnel3MDAQArWI0eFhISE87nb7bZ7PJ4VvLYuLi5O5+fnu9+kMNfq6+tLjIyMzMY6KeBEbK/XarXReI3lPDZMWcc4v7GxYV1dXR3Jy8ub2E5HPvJ6vRSSDH0ku1wuAfsEZOV1IEFHoeNFdHS0yMrK2knR0Lm5uR+hxLdQMjbwHTZbB41h8RGwCdc9MzMzneHh4bGJiYlf4SN8ijkfwqiIncCAAR7Iz2GPSShudjqdfeCeqampvwBQfFxc3JdYqwTv8gB8/F48A8BgKecE14V+L7ju2tpae05OzkuCCZvkPOj8mizmC6vVKtmPu+bx48cC3qI1mUyFUOyywWD4SHlELBaLJmCHNcwAghuAOujtuF4FqHO4nsX4EsAS3I4TJ04ME1h8PDE9PS09TYZoY2Pj1729vd6lpSVfkDYTPG0UkfNDRUWFgQ5Gb2Mh0N29e9eG/GQfHh4W8/PzwUy/ObQ/gMfVVlZW1iAiZdQxp3nv3LljRoL/5erVq1UIxzSiiVD9X4EDYATynCwAzGO858hCQRoaGmJFZNJz8YIcBc4BF966dau6sLAwBxVSJCUlCSThQwuU3W6XkYUok1Vzm5znQx5bbm9v77p+/frPeNSNRzZ/ISBwrG4ZR48eLamtrf2+uLjYSEG9Xi/wTISFhQlWGXohyzO/CJlVl23KQRLbABoaHx+/Z1lUZ/Hq1SsJFj3JT3hmHx8fnydPTEzMj46OziHPW2w22wxeD4Kfgadh/4YEzU8Az4DhffAn5eXlX1y6dKkEoCTspAQ9Mjs7+0BBo8Fms1lkZGTsOo0QLLRNkvnR+fEJzIMHD0xtbW39CL8JTFtSbAOvBIyLHIGVm9VzE2gKuDAMSSpcT6KXyT137lx2cnLyMXhcGDb3wq3XuWF3d/fCzZs3P0c4v5eSknJQbYLo7Ox0gC2lpaVZ3Be67Th/dnZWoAJKsJC3XA8fPhxoamp6hMb+BaaMgWcUMGtszZjiFDNmvcDI91pzG0iY4ARwkwrxkcHBwUdgNrRMbnrqoRbkVzDcvn3bl5qaWsmcgFH4G8XdEGUWFhak51AuISFBnkoCTyFbyWKxCJwIxlC0fq2rq7tcVFRkRKskjh8/Lr0+kBjCCDV/knfdv3//WX19/R8IRRNemxlu4AXwKqM+EJwdj1HbPYSwh3sCPAJDABm2LLchCjS+5/kirKGhwWk0GrMuXrxYQuX9hm/XXTMXMY+srKwI5ApZrbYmZh7deEJhAUKjLe/pLTzSsCuHrK+1tbUJVe3P6upq87Vr174rKysrYHVj/uW+OH3IfEuw4F3ee/fuPQfAvwOs5yyE4CnlFOu7BWrTCWlreO6FACpBZGwUw4BvkANLobReHb3kGZYGsGzTq/zlO8AT1ru6uoZbWlqeA6gINJAfnz59OlVLoX8Jtebm5raampqfcMvQYgTknz9//sKVK1c+y83NTdIEuCnaKMuNGzd+6+np6cCtSTkAw9D9X8Dyh+dbgaaAC1XAnUlPTy+qqqq6cPbs2UzkmWjNljiDJzpwHFnCkW2yo6NjCKW8H54wjlezKvRT09LSTsJrz5w6dSoN+Yp51ADAPUj8VoDbDq9pxrwuJcNIYQllJTIi/xopBw/VA7DJp0+f9hA78CgL5F5C8J2CpoCj8sfA6WCe/FPRhsRlZmbGIs8Y4FFO5CJgtrSsvrRVGW1V93b1myoGnKAKEcHgnwsWpg1lNI0fphwrmdqbckeU18WrnlOjqp5/j7W3BWvfQVPKa5SBkcrYCNVB65TRTlWZ1lXiXVU5xbtlDb2SPaLWYwrgHIcqPg6Vc7fbX69Yoyqfa7/AeiegbWOEVhmsVcWDwPn224iDJgla8Hd38Hd3ELQgaIeI/hZgAIPEp0vmQJdoAAAAAElFTkSuQmCC)
no-repeat 50% 0;
background-size: 64px;
opacity: 1;
}

32
components/button/style/mixin.less

@ -24,17 +24,21 @@
.button-variant-primary(@color; @background) {
.button-color(@color; @background; @background);
text-shadow: 0 -1px 0 rgba(0, 0, 0, .12);
box-shadow: 0 2px 0 rgba(0, 0, 0, .045);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
&:hover,
&:focus {
.button-color(@color; ~`colorPalette("@{background}", 5)`; ~`colorPalette("@{background}", 5)`);
.button-color(
@color; ~`colorPalette('@{background}', 5) `; ~`colorPalette('@{background}', 5) `
);
}
&:active,
&.active {
.button-color(@color; ~`colorPalette("@{background}", 7)`; ~`colorPalette("@{background}", 7)`);
.button-color(
@color; ~`colorPalette('@{background}', 7) `; ~`colorPalette('@{background}', 7) `
);
}
.button-disabled();
@ -60,16 +64,20 @@
.button-color(@color; @background; @border);
&:hover {
.button-color(@btn-primary-color; ~`colorPalette("@{color}", 5)`; ~`colorPalette("@{color}", 5)`);
.button-color(
@btn-primary-color; ~`colorPalette('@{color}', 5) `; ~`colorPalette('@{color}', 5) `
);
}
&:focus {
.button-color(~`colorPalette("@{color}", 5)`; #fff; ~`colorPalette("@{color}", 5)`);
.button-color(~`colorPalette('@{color}', 5) `; #fff; ~`colorPalette('@{color}', 5) `);
}
&:active,
&.active {
.button-color(@btn-primary-color; ~`colorPalette("@{color}", 7)`; ~`colorPalette("@{color}", 7)`);
.button-color(
@btn-primary-color; ~`colorPalette('@{color}', 7) `; ~`colorPalette('@{color}', 7) `
);
}
.button-disabled();
@ -81,12 +89,12 @@
&:hover,
&:focus {
.button-color(~`colorPalette("@{color}", 5)`; transparent; ~`colorPalette("@{color}", 5)`);
.button-color(~`colorPalette('@{color}', 5) `; transparent; ~`colorPalette('@{color}', 5) `);
}
&:active,
&.active {
.button-color(~`colorPalette("@{color}", 7)`; transparent; ~`colorPalette("@{color}", 7)`);
.button-color(~`colorPalette('@{color}', 7) `; transparent; ~`colorPalette('@{color}', 7) `);
}
.button-disabled();
@ -101,7 +109,7 @@
> a:only-child {
color: currentColor;
&:after {
content: "";
content: '';
position: absolute;
top: 0;
left: 0;
@ -161,9 +169,9 @@
white-space: nowrap;
.button-size(@btn-height-base; @btn-padding-base; @font-size-base; @btn-border-radius-base);
user-select: none;
transition: all .3s @ease-in-out;
transition: all 0.3s @ease-in-out;
position: relative;
box-shadow: 0 2px 0 rgba(0, 0, 0, .015);
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
> .@{iconfont-css-prefix} {
line-height: 1;

18
components/calendar/Header.tsx

@ -15,7 +15,7 @@ export interface HeaderProps {
onValueChange?: (value: moment.Moment) => void;
onTypeChange?: (type: string) => void;
value: any;
validRange ?: [moment.Moment, moment.Moment];
validRange?: [moment.Moment, moment.Moment];
}
export default class Header extends React.Component<HeaderProps, any> {
@ -110,7 +110,7 @@ export default class Header extends React.Component<HeaderProps, any> {
newValue.year(parseInt(year, 10));
// switch the month so that it remains within range when year changes
if (validRange) {
const [ start, end ] = validRange;
const [start, end] = validRange;
const newYear = newValue.get('year');
const newMonth = newValue.get('month');
if (newYear === end.get('year') && newMonth > end.get('month')) {
@ -125,7 +125,7 @@ export default class Header extends React.Component<HeaderProps, any> {
if (onValueChange) {
onValueChange(newValue);
}
}
};
onMonthChange = (month: string) => {
const newValue = this.props.value.clone();
@ -134,24 +134,26 @@ export default class Header extends React.Component<HeaderProps, any> {
if (onValueChange) {
onValueChange(newValue);
}
}
};
onTypeChange = (e: RadioChangeEvent) => {
const onTypeChange = this.props.onTypeChange;
if (onTypeChange) {
onTypeChange(e.target.value);
}
}
};
getCalenderHeaderNode = (node: HTMLDivElement) => {
this.calenderHeaderNode = node;
}
};
render() {
const { type, value, prefixCls, locale, fullscreen } = this.props;
const yearSelect = this.getYearSelectElement(value.year());
const monthSelect = type === 'date' ?
this.getMonthSelectElement(value.month(), this.getMonthsLocale(value)) : null;
const monthSelect =
type === 'date'
? this.getMonthSelectElement(value.month(), this.getMonthsLocale(value))
: null;
const size = (fullscreen ? 'default' : 'small') as any;
const typeSwitch = (
<Group onChange={this.onTypeChange} value={type} size={size}>

105
components/calendar/__tests__/index.test.js

@ -7,10 +7,11 @@ import Calendar from '..';
describe('Calendar', () => {
it('Calendar should be selectable', () => {
const onSelect = jest.fn();
const wrapper = mount(
<Calendar onSelect={onSelect} />
);
wrapper.find('.ant-fullcalendar-cell').at(0).simulate('click');
const wrapper = mount(<Calendar onSelect={onSelect} />);
wrapper
.find('.ant-fullcalendar-cell')
.at(0)
.simulate('click');
expect(onSelect).toBeCalledWith(expect.anything());
const value = onSelect.mock.calls[0][0];
expect(Moment.isMoment(value)).toBe(true);
@ -20,10 +21,16 @@ describe('Calendar', () => {
const onSelect = jest.fn();
const validRange = [Moment('2018-02-02'), Moment('2018-02-18')];
const wrapper = mount(
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />,
);
wrapper.find('[title="February 1, 2018"]').at(0).simulate('click');
wrapper.find('[title="February 2, 2018"]').at(0).simulate('click');
wrapper
.find('[title="February 1, 2018"]')
.at(0)
.simulate('click');
wrapper
.find('[title="February 2, 2018"]')
.at(0)
.simulate('click');
expect(onSelect.mock.calls.length).toBe(1);
});
@ -31,10 +38,15 @@ describe('Calendar', () => {
const onSelect = jest.fn();
const validRange = [Moment('2018-02-02'), Moment('2018-02-18')];
const wrapper = mount(
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />,
);
wrapper.find('[title="February 20, 2018"]').at(0).simulate('click');
const elem = wrapper.find('[title="February 20, 2018"]').hasClass('ant-fullcalendar-disabled-cell');
wrapper
.find('[title="February 20, 2018"]')
.at(0)
.simulate('click');
const elem = wrapper
.find('[title="February 20, 2018"]')
.hasClass('ant-fullcalendar-disabled-cell');
expect(elem).toEqual(true);
expect(onSelect.mock.calls.length).toBe(0);
});
@ -43,33 +55,64 @@ describe('Calendar', () => {
const onSelect = jest.fn();
const validRange = [Moment('2018-02-02'), Moment('2018-05-18')];
const wrapper = mount(
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} mode="year" />
<Calendar
onSelect={onSelect}
validRange={validRange}
defaultValue={Moment('2018-02-02')}
mode="year"
/>,
);
expect(wrapper.find('[title="Jan"]').at(0).hasClass('ant-fullcalendar-month-panel-cell-disabled')).toBe(true);
expect(wrapper.find('[title="Feb"]').at(0).hasClass('ant-fullcalendar-month-panel-cell-disabled')).toBe(false);
expect(wrapper.find('[title="Jun"]').at(0).hasClass('ant-fullcalendar-month-panel-cell-disabled')).toBe(true);
wrapper.find('[title="Jan"]').at(0).simulate('click');
wrapper.find('[title="Mar"]').at(0).simulate('click');
expect(
wrapper
.find('[title="Jan"]')
.at(0)
.hasClass('ant-fullcalendar-month-panel-cell-disabled'),
).toBe(true);
expect(
wrapper
.find('[title="Feb"]')
.at(0)
.hasClass('ant-fullcalendar-month-panel-cell-disabled'),
).toBe(false);
expect(
wrapper
.find('[title="Jun"]')
.at(0)
.hasClass('ant-fullcalendar-month-panel-cell-disabled'),
).toBe(true);
wrapper
.find('[title="Jan"]')
.at(0)
.simulate('click');
wrapper
.find('[title="Mar"]')
.at(0)
.simulate('click');
expect(onSelect.mock.calls.length).toBe(1);
});
it('months other than in valid range should not be shown in header', () => {
const validRange = [Moment('2017-02-02'), Moment('2018-05-18')];
const wrapper = mount(
<Calendar validRange={validRange} />
);
wrapper.find('.ant-fullcalendar-year-select').hostNodes().simulate('click');
wrapper.find('.ant-select-dropdown-menu-item').first().simulate('click');
wrapper.find('.ant-fullcalendar-month-select').hostNodes().simulate('click');
const wrapper = mount(<Calendar validRange={validRange} />);
wrapper
.find('.ant-fullcalendar-year-select')
.hostNodes()
.simulate('click');
wrapper
.find('.ant-select-dropdown-menu-item')
.first()
.simulate('click');
wrapper
.find('.ant-fullcalendar-month-select')
.hostNodes()
.simulate('click');
// 2 years and 11 months
expect(wrapper.find('.ant-select-dropdown-menu-item').length).toBe(13);
});
it('getDateRange should returns a disabledDate function', () => {
const validRange = [Moment('2018-02-02'), Moment('2018-05-18')];
const wrapper = mount(
<Calendar validRange={validRange} defaultValue={Moment('2018-02-02')} />
);
const wrapper = mount(<Calendar validRange={validRange} defaultValue={Moment('2018-02-02')} />);
const instance = wrapper.instance();
const disabledDate = instance.getDateRange(validRange);
expect(disabledDate(Moment('2018-06-02'))).toBe(true);
@ -79,9 +122,7 @@ describe('Calendar', () => {
it('Calendar should change mode by prop', () => {
const monthMode = 'month';
const yearMode = 'year';
const wrapper = mount(
<Calendar />
);
const wrapper = mount(<Calendar />);
expect(wrapper.state().mode).toEqual(monthMode);
wrapper.setProps({ mode: 'year' });
expect(wrapper.state().mode).toEqual(yearMode);
@ -91,9 +132,7 @@ describe('Calendar', () => {
const monthMode = 'month';
const yearMode = 'year';
const onPanelChangeStub = jest.fn();
const wrapper = mount(
<Calendar mode={yearMode} onPanelChange={onPanelChangeStub} />
);
const wrapper = mount(<Calendar mode={yearMode} onPanelChange={onPanelChangeStub} />);
expect(wrapper.state().mode).toEqual(yearMode);
wrapper.instance().setType('date');
expect(wrapper.state().mode).toEqual(monthMode);
@ -104,9 +143,7 @@ describe('Calendar', () => {
MockDate.set(Moment('2018-10-19'));
// eslint-disable-next-line
const zhCN = require('../locale/zh_CN').default;
const wrapper = mount(
<Calendar locale={zhCN} />
);
const wrapper = mount(<Calendar locale={zhCN} />);
expect(wrapper.render()).toMatchSnapshot();
MockDate.reset();
});

70
components/calendar/index.tsx

@ -10,7 +10,9 @@ import enUS from './locale/en_US';
export { HeaderProps } from './Header';
function noop() { return null; }
function noop() {
return null;
}
function zerofixed(v: number) {
if (v < 10) {
@ -38,7 +40,7 @@ export interface CalendarProps {
onSelect?: (date?: moment.Moment) => void;
onChange?: (date?: moment.Moment) => void;
disabledDate?: (current: moment.Moment) => boolean;
validRange ?: [moment.Moment, moment.Moment];
validRange?: [moment.Moment, moment.Moment];
}
export interface CalendarState {
@ -80,7 +82,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
if (!interopDefault(moment).isMoment(value)) {
throw new Error(
'The value/defaultValue of Calendar must be a moment object after `antd@2.0`, ' +
'see: https://u.ant.design/calendar-value',
'see: https://u.ant.design/calendar-value',
);
}
this.state = {
@ -97,7 +99,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
}
if ('mode' in nextProps && nextProps.mode !== this.props.mode) {
this.setState({
mode: nextProps.mode!,
mode: nextProps.mode!,
});
}
}
@ -106,29 +108,21 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
const { prefixCls, monthCellRender = noop as Function } = this.props;
return (
<div className={`${prefixCls}-month`}>
<div className={`${prefixCls}-value`}>
{value.localeData().monthsShort(value)}
</div>
<div className={`${prefixCls}-content`}>
{monthCellRender(value)}
</div>
<div className={`${prefixCls}-value`}>{value.localeData().monthsShort(value)}</div>
<div className={`${prefixCls}-content`}>{monthCellRender(value)}</div>
</div>
);
}
};
dateCellRender = (value: moment.Moment) => {
const { prefixCls, dateCellRender = noop as Function } = this.props;
return (
<div className={`${prefixCls}-date`}>
<div className={`${prefixCls}-value`}>
{zerofixed(value.date())}
</div>
<div className={`${prefixCls}-content`}>
{dateCellRender(value)}
</div>
<div className={`${prefixCls}-value`}>{zerofixed(value.date())}</div>
<div className={`${prefixCls}-content`}>{dateCellRender(value)}</div>
</div>
);
}
};
setValue = (value: moment.Moment, way: 'select' | 'changePanel') => {
if (!('value' in this.props)) {
@ -141,23 +135,23 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
} else if (way === 'changePanel') {
this.onPanelChange(value, this.state.mode);
}
}
};
setType = (type: string) => {
const mode = (type === 'date') ? 'month' : 'year';
const mode = type === 'date' ? 'month' : 'year';
if (this.state.mode !== mode) {
this.setState({ mode });
this.onPanelChange(this.state.value, mode);
}
}
};
onHeaderValueChange = (value: moment.Moment) => {
this.setValue(value, 'changePanel');
}
};
onHeaderTypeChange = (type: string) => {
this.setType(type);
}
};
onPanelChange(value: moment.Moment, mode: CalendarMode | undefined) {
const { onPanelChange, onChange } = this.props;
@ -171,7 +165,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
onSelect = (value: moment.Moment) => {
this.setValue(value, 'select');
}
};
getDateRange = (
validRange: [moment.Moment, moment.Moment],
@ -180,13 +174,13 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
if (!current) {
return false;
}
const [ startDate, endDate ] = validRange;
const [startDate, endDate] = validRange;
const inRange = !current.isBetween(startDate, endDate, 'days', '[]');
if (disabledDate) {
return (disabledDate(current) || inRange);
return disabledDate(current) || inRange;
}
return inRange;
}
};
renderCalendar = (locale: any, localeCode: string) => {
const { state, props } = this;
@ -194,12 +188,19 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
if (value && localeCode) {
value.locale(localeCode);
}
const { prefixCls, style, className, fullscreen, dateFullCellRender, monthFullCellRender } = props;
const type = (mode === 'year') ? 'month' : 'date';
const {
prefixCls,
style,
className,
fullscreen,
dateFullCellRender,
monthFullCellRender,
} = props;
const type = mode === 'year' ? 'month' : 'date';
let cls = className || '';
if (fullscreen) {
cls += (` ${prefixCls}-fullscreen`);
cls += ` ${prefixCls}-fullscreen`;
}
const monthCellRender = monthFullCellRender || this.monthCellRender;
@ -238,7 +239,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
/>
</div>
);
}
};
getDefaultLocale = () => {
const result = {
@ -250,14 +251,11 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
...(this.props.locale || {}).lang,
};
return result;
}
};
render() {
return (
<LocaleReceiver
componentName="Calendar"
defaultLocale={this.getDefaultLocale}
>
<LocaleReceiver componentName="Calendar" defaultLocale={this.getDefaultLocale}>
{this.renderCalendar}
</LocaleReceiver>
);

12
components/calendar/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@full-calendar-prefix-cls: ~"@{ant-prefix}-fullcalendar";
@full-calendar-prefix-cls: ~'@{ant-prefix}-fullcalendar';
.@{full-calendar-prefix-cls} {
.reset-component;
@ -84,7 +84,7 @@
&-month,
&-date {
text-align: center;
transition: all .3s;
transition: all 0.3s;
}
&-value {
@ -97,7 +97,7 @@
padding: 0;
background: transparent;
line-height: 24px;
transition: all .3s;
transition: all 0.3s;
&:hover {
background: @item-hover-bg;
@ -180,7 +180,7 @@
height: 116px;
padding: 4px 8px;
border-top: 2px solid @border-color-split;
transition: background .3s;
transition: background 0.3s;
&:hover {
background: @item-hover-bg;

17
components/card/Meta.tsx

@ -15,13 +15,16 @@ export default (props: CardMetaProps) => {
const classString = classNames(`${prefixCls}-meta`, className);
const avatarDom = avatar ? <div className={`${prefixCls}-meta-avatar`}>{avatar}</div> : null;
const titleDom = title ? <div className={`${prefixCls}-meta-title`}>{title}</div> : null;
const descriptionDom = description ?
<div className={`${prefixCls}-meta-description`}>{description}</div> : null;
const MetaDetail = titleDom || descriptionDom ?
<div className={`${prefixCls}-meta-detail`}>
{titleDom}
{descriptionDom}
</div> : null;
const descriptionDom = description ? (
<div className={`${prefixCls}-meta-description`}>{description}</div>
) : null;
const MetaDetail =
titleDom || descriptionDom ? (
<div className={`${prefixCls}-meta-detail`}>
{titleDom}
{descriptionDom}
</div>
) : null;
return (
<div {...others} className={classString}>
{avatarDom}

12
components/card/__tests__/index.test.js

@ -17,7 +17,9 @@ describe('Card', () => {
function fakeResizeWindowTo(wrapper, width) {
Object.defineProperties(wrapper.instance().container, {
offsetWidth: {
get() { return width; },
get() {
return width;
},
configurable: true,
},
});
@ -37,7 +39,11 @@ describe('Card', () => {
});
it('should still have padding when card which set padding to 0 is loading', () => {
const wrapper = mount(<Card loading bodyStyle={{ padding: 0 }}>xxx</Card>);
const wrapper = mount(
<Card loading bodyStyle={{ padding: 0 }}>
xxx
</Card>,
);
expect(wrapper.render()).toMatchSnapshot();
});
@ -45,7 +51,7 @@ describe('Card', () => {
const wrapper = mount(
<Card title="Card title" extra={<Button>Button</Button>} style={{ width: 300 }}>
<p>Card content</p>
</Card>
</Card>,
);
expect(wrapper.render()).toMatchSnapshot();
});

80
components/card/index.tsx

@ -70,7 +70,10 @@ export default class Card extends React.Component<CardProps, CardState> {
!this.props.noHovering,
'`noHovering` of Card is deprecated, you can remove it safely or use `hoverable` instead.',
);
warning(!!this.props.noHovering, '`noHovering={false}` of Card is deprecated, use `hoverable` instead.');
warning(
!!this.props.noHovering,
'`noHovering={false}` of Card is deprecated, use `hoverable` instead.',
);
}
}
@ -104,11 +107,11 @@ export default class Card extends React.Component<CardProps, CardState> {
if (this.props.onTabChange) {
this.props.onTabChange(key);
}
}
};
saveRef = (node: HTMLDivElement) => {
this.container = node;
}
};
isContainGrid() {
let containGrid;
@ -125,11 +128,10 @@ export default class Card extends React.Component<CardProps, CardState> {
return null;
}
const actionList = actions.map((action, index) => (
<li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
<span>{action}</span>
</li>
),
);
<li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
<span>{action}</span>
</li>
));
return actionList;
}
@ -144,8 +146,24 @@ export default class Card extends React.Component<CardProps, CardState> {
render() {
const {
prefixCls = 'ant-card', className, extra, headStyle = {}, bodyStyle = {}, noHovering, hoverable, title, loading,
bordered = true, type, cover, actions, tabList, children, activeTabKey, defaultActiveTabKey, ...others
prefixCls = 'ant-card',
className,
extra,
headStyle = {},
bodyStyle = {},
noHovering,
hoverable,
title,
loading,
bordered = true,
type,
cover,
actions,
tabList,
children,
activeTabKey,
defaultActiveTabKey,
...others
} = this.props;
const classString = classNames(prefixCls, className, {
@ -159,14 +177,11 @@ export default class Card extends React.Component<CardProps, CardState> {
[`${prefixCls}-type-${type}`]: !!type,
});
const loadingBlockStyle = (bodyStyle.padding === 0 || bodyStyle.padding === '0px')
? { padding: 24 } : undefined;
const loadingBlockStyle =
bodyStyle.padding === 0 || bodyStyle.padding === '0px' ? { padding: 24 } : undefined;
const loadingBlock = (
<div
className={`${prefixCls}-loading-content`}
style={loadingBlockStyle}
>
<div className={`${prefixCls}-loading-content`} style={loadingBlockStyle}>
<Row gutter={8}>
<Col span={22}>
<div className={`${prefixCls}-loading-block`} />
@ -229,16 +244,19 @@ export default class Card extends React.Component<CardProps, CardState> {
};
let head;
const tabs = tabList && tabList.length ? (
<Tabs
{...extraProps}
className={`${prefixCls}-head-tabs`}
size="large"
onChange={this.onTabChange}
>
{tabList.map(item => <Tabs.TabPane tab={item.tab} disabled={item.disabled} key={item.key} />)}
</Tabs>
) : null;
const tabs =
tabList && tabList.length ? (
<Tabs
{...extraProps}
className={`${prefixCls}-head-tabs`}
size="large"
onChange={this.onTabChange}
>
{tabList.map(item => (
<Tabs.TabPane tab={item.tab} disabled={item.disabled} key={item.key} />
))}
</Tabs>
) : null;
if (title || extra || tabs) {
head = (
<div className={`${prefixCls}-head`} style={headStyle}>
@ -256,11 +274,11 @@ export default class Card extends React.Component<CardProps, CardState> {
{loading ? loadingBlock : children}
</div>
);
const actionDom = actions && actions.length ?
<ul className={`${prefixCls}-actions`}>{this.getAction(actions)}</ul> : null;
const divProps = omit(others, [
'onTabChange',
]);
const actionDom =
actions && actions.length ? (
<ul className={`${prefixCls}-actions`}>{this.getAction(actions)}</ul>
) : null;
const divProps = omit(others, ['onTabChange']);
return (
<div {...divProps} className={classString} ref={this.saveRef}>
{head}

35
components/card/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@card-prefix-cls: ~"@{ant-prefix}-card";
@card-prefix-cls: ~'@{ant-prefix}-card';
@card-head-height: 48px;
@card-hover-border: rgba(0, 0, 0, 0.09);
@card-radius: @border-radius-sm;
@ -11,7 +11,7 @@
background: @component-background;
border-radius: @card-radius;
position: relative;
transition: all .3s;
transition: all 0.3s;
&-hoverable {
cursor: pointer;
@ -88,11 +88,13 @@
&-grid {
border-radius: 0;
border: 0;
box-shadow: 1px 0 0 0 @border-color-split, 0 1px 0 0 @border-color-split, 1px 1px 0 0 @border-color-split, 1px 0 0 0 @border-color-split inset, 0 1px 0 0 @border-color-split inset;
box-shadow: 1px 0 0 0 @border-color-split, 0 1px 0 0 @border-color-split,
1px 1px 0 0 @border-color-split, 1px 0 0 0 @border-color-split inset,
0 1px 0 0 @border-color-split inset;
width: 33.33%;
float: left;
padding: @card-padding-base;
transition: all .3s;
transition: all 0.3s;
&:hover {
position: relative;
z-index: 1;
@ -143,7 +145,7 @@
&:hover {
color: @primary-color;
transition: color .3s;
transition: color 0.3s;
}
& > .anticon {
@ -179,7 +181,7 @@
&-padding-transition &-head,
&-padding-transition &-body {
transition: padding .3s;
transition: padding 0.3s;
}
&-type-inner &-head {
@ -244,7 +246,12 @@
height: 14px;
margin: 4px 0;
border-radius: @card-radius;
background: linear-gradient(90deg, rgba(207, 216, 220, .2), rgba(207, 216, 220, .4), rgba(207, 216, 220, .2));
background: linear-gradient(
90deg,
rgba(207, 216, 220, 0.2),
rgba(207, 216, 220, 0.4),
rgba(207, 216, 220, 0.2)
);
animation: card-loading 1.4s ease infinite;
background-size: 600% 600%;
}
@ -253,9 +260,9 @@
@keyframes card-loading {
0%,
100% {
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
}

30
components/carousel/__tests__/index.test.js

@ -12,7 +12,11 @@ describe('Carousel', () => {
});
it('should has innerSlider', () => {
const wrapper = mount(<Carousel><div /></Carousel>);
const wrapper = mount(
<Carousel>
<div />
</Carousel>,
);
const { innerSlider } = wrapper.instance();
const innerSliderFromRefs = wrapper.instance().slick.innerSlider;
expect(innerSlider).toBe(innerSliderFromRefs);
@ -20,7 +24,13 @@ describe('Carousel', () => {
});
it('should has prev, next and go function', () => {
const wrapper = mount(<Carousel><div>1</div><div>2</div><div>3</div></Carousel>);
const wrapper = mount(
<Carousel>
<div>1</div>
<div>2</div>
<div>3</div>
</Carousel>,
);
const { prev, next, goTo } = wrapper.instance();
expect(typeof prev).toBe('function');
expect(typeof next).toBe('function');
@ -39,7 +49,13 @@ describe('Carousel', () => {
it('should trigger autoPlay after window resize', async () => {
jest.useRealTimers();
const wrapper = mount(<Carousel autoplay><div>1</div><div>2</div><div>3</div></Carousel>);
const wrapper = mount(
<Carousel autoplay>
<div>1</div>
<div>2</div>
<div>3</div>
</Carousel>,
);
const spy = jest.spyOn(wrapper.instance().slick.innerSlider, 'autoPlay');
window.resizeTo(1000);
expect(spy).not.toBeCalled();
@ -48,7 +64,13 @@ describe('Carousel', () => {
});
it('cancel resize listener when unmount', async () => {
const wrapper = mount(<Carousel autoplay><div>1</div><div>2</div><div>3</div></Carousel>);
const wrapper = mount(
<Carousel autoplay>
<div>1</div>
<div>2</div>
<div>3</div>
</Carousel>,
);
const { onWindowResized } = wrapper.instance();
const spy = jest.spyOn(wrapper.instance().onWindowResized, 'cancel');
const spy2 = jest.spyOn(window, 'removeEventListener');

10
components/carousel/index.tsx

@ -8,10 +8,8 @@ if (typeof window !== 'undefined') {
return {
media: mediaQuery,
matches: false,
addListener() {
},
removeListener() {
},
addListener() {},
removeListener() {},
};
};
window.matchMedia = window.matchMedia || matchMediaPolyfill;
@ -108,11 +106,11 @@ export default class Carousel extends React.Component<CarouselProps, {}> {
if (autoplay && this.slick && this.slick.innerSlider && this.slick.innerSlider.autoPlay) {
this.slick.innerSlider.autoPlay();
}
}
};
saveSlick = (node: any) => {
this.slick = node;
}
};
next() {
this.slick.slickNext();

14
components/carousel/style/index.less

@ -1,5 +1,5 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
.@{ant-prefix}-carousel {
.reset-component;
@ -49,7 +49,7 @@
&:before,
&:after {
content: "";
content: '';
display: table;
}
@ -65,7 +65,7 @@
float: left;
height: 100%;
min-height: 1px;
[dir="rtl"] & {
[dir='rtl'] & {
float: right;
}
img {
@ -133,14 +133,14 @@
.slick-prev {
left: -25px;
&:before {
content: "←";
content: '←';
}
}
.slick-next {
right: -25px;
&:before {
content: "→";
content: '→';
}
}
@ -174,7 +174,7 @@
outline: none;
font-size: 0;
color: transparent;
transition: all .5s;
transition: all 0.5s;
padding: 0;
&:hover,
&:focus {

363
components/cascader/__tests__/index.test.js

@ -4,58 +4,79 @@ import KeyCode from 'rc-util/lib/KeyCode';
import Cascader from '..';
import focusTest from '../../../tests/shared/focusTest';
const options = [{
value: 'zhejiang',
label: 'Zhejiang',
children: [{
value: 'hangzhou',
label: 'Hangzhou',
children: [{
value: 'xihu',
label: 'West Lake',
}],
}],
}, {
value: 'jiangsu',
label: 'Jiangsu',
children: [{
value: 'nanjing',
label: 'Nanjing',
children: [{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
}],
}],
}];
const options = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
function filter(inputValue, path) {
return path.some(option => (option.label).toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
}
describe('Cascader', () => {
focusTest(Cascader);
it('popup correctly when panel is hidden', () => {
const wrapper = mount(
<Cascader options={options} />
);
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
const wrapper = mount(<Cascader options={options} />);
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('popup correctly when panel is open', () => {
const onPopupVisibleChange = jest.fn();
const wrapper = mount(
<Cascader options={options} onPopupVisibleChange={onPopupVisibleChange} />
<Cascader options={options} onPopupVisibleChange={onPopupVisibleChange} />,
);
wrapper.find('input').simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
expect(onPopupVisibleChange).toHaveBeenCalledWith(true);
});
it('support controlled mode', () => {
const wrapper = mount(
<Cascader options={options} />
);
const wrapper = mount(<Cascader options={options} />);
wrapper.setProps({
value: ['zhejiang', 'hangzhou', 'xihu'],
});
@ -63,38 +84,99 @@ describe('Cascader', () => {
});
it('popup correctly with defaultValue', () => {
const wrapper = mount(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />
);
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
wrapper.find('input').simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('should support popupVisible', () => {
const wrapper = mount(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />
);
expect(wrapper.find('Trigger').instance().getComponent().props.visible).toBe(false);
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
expect(
wrapper
.find('Trigger')
.instance()
.getComponent().props.visible,
).toBe(false);
wrapper.setProps({ popupVisible: true });
expect(wrapper.find('Trigger').instance().getComponent().props.visible).toBe(true);
expect(
wrapper
.find('Trigger')
.instance()
.getComponent().props.visible,
).toBe(true);
});
it('can be selected', () => {
const onChange = jest.fn();
const wrapper = mount(<Cascader options={options} onChange={onChange} />);
wrapper.find('input').simulate('click');
let popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
popupWrapper.find('.ant-cascader-menu').at(0).find('.ant-cascader-menu-item').at(0)
let popupWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
popupWrapper
.find('.ant-cascader-menu')
.at(0)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
popupWrapper.find('.ant-cascader-menu').at(1).find('.ant-cascader-menu-item').at(0)
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
popupWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
popupWrapper
.find('.ant-cascader-menu')
.at(1)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
popupWrapper.find('.ant-cascader-menu').at(2).find('.ant-cascader-menu-item').at(0)
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
popupWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
popupWrapper
.find('.ant-cascader-menu')
.at(2)
.find('.ant-cascader-menu-item')
.at(0)
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
@ -112,7 +194,12 @@ describe('Cascader', () => {
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'z' } });
expect(wrapper.state('inputValue')).toBe('z');
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
const popupWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(popupWrapper).toMatchSnapshot();
});
@ -121,14 +208,22 @@ describe('Cascader', () => {
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: '__notfoundkeyword__' } });
expect(wrapper.state('inputValue')).toBe('__notfoundkeyword__');
const popupWrapper = mount(wrapper.find('Trigger').instance().getComponent());
const popupWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(popupWrapper).toMatchSnapshot();
});
it('should support to clear selection', () => {
const wrapper = mount(<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />);
expect(wrapper.find('.ant-cascader-picker-label').text()).toBe('Zhejiang / Hangzhou');
wrapper.find('.ant-cascader-picker-clear').at(0).simulate('click');
wrapper
.find('.ant-cascader-picker-clear')
.at(0)
.simulate('click');
expect(wrapper.find('.ant-cascader-picker-label').text()).toBe('');
});
@ -140,35 +235,33 @@ describe('Cascader', () => {
popupVisible
defaultValue={['zhejiang', 'hangzhou']}
onPopupVisibleChange={onPopupVisibleChange}
/>
/>,
);
wrapper.find('.ant-cascader-picker-clear').at(0).simulate('click');
wrapper
.find('.ant-cascader-picker-clear')
.at(0)
.simulate('click');
expect(onPopupVisibleChange).toHaveBeenCalledWith(false);
});
it('should clear search input when clear selection', () => {
const wrapper = mount(
<Cascader
options={options}
defaultValue={['zhejiang', 'hangzhou']}
showSearch
/>
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} showSearch />,
);
wrapper.find('input').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'xxx' } });
expect(wrapper.state('inputValue')).toBe('xxx');
wrapper.find('.ant-cascader-picker-clear').at(0).simulate('click');
wrapper
.find('.ant-cascader-picker-clear')
.at(0)
.simulate('click');
expect(wrapper.state('inputValue')).toBe('');
});
it('should not trigger visible change when click search input', () => {
const onPopupVisibleChange = jest.fn();
const wrapper = mount(
<Cascader
options={options}
showSearch
onPopupVisibleChange={onPopupVisibleChange}
/>
<Cascader options={options} showSearch onPopupVisibleChange={onPopupVisibleChange} />,
);
wrapper.find('input').simulate('focus');
expect(onPopupVisibleChange).toHaveBeenCalledTimes(0);
@ -192,29 +285,40 @@ describe('Cascader', () => {
});
it('can use fieldNames', () => {
const customerOptions = [{
code: 'zhejiang',
name: 'Zhejiang',
items: [{
code: 'hangzhou',
name: 'Hangzhou',
items: [{
code: 'xihu',
name: 'West Lake',
}],
}],
}, {
code: 'jiangsu',
name: 'Jiangsu',
items: [{
code: 'nanjing',
name: 'Nanjing',
items: [{
code: 'zhonghuamen',
name: 'Zhong Hua Men',
}],
}],
}];
const customerOptions = [
{
code: 'zhejiang',
name: 'Zhejiang',
items: [
{
code: 'hangzhou',
name: 'Hangzhou',
items: [
{
code: 'xihu',
name: 'West Lake',
},
],
},
],
},
{
code: 'jiangsu',
name: 'Jiangsu',
items: [
{
code: 'nanjing',
name: 'Nanjing',
items: [
{
code: 'zhonghuamen',
name: 'Zhong Hua Men',
},
],
},
],
},
];
const wrapper = mount(
<Cascader
options={customerOptions}
@ -223,38 +327,54 @@ describe('Cascader', () => {
label: 'name',
value: 'code',
}}
/>
/>,
);
wrapper.instance().handleChange(['zhejiang', 'hangzhou', 'xihu'], customerOptions);
expect(wrapper.find('.ant-cascader-picker-label').text().split('/').length).toBe(3);
expect(
wrapper
.find('.ant-cascader-picker-label')
.text()
.split('/').length,
).toBe(3);
});
// https://github.com/ant-design/ant-design/issues/12970
it('can use filedNames too, for compatibility', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const customerOptions = [{
code: 'zhejiang',
name: 'Zhejiang',
items: [{
code: 'hangzhou',
name: 'Hangzhou',
items: [{
code: 'xihu',
name: 'West Lake',
}],
}],
}, {
code: 'jiangsu',
name: 'Jiangsu',
items: [{
code: 'nanjing',
name: 'Nanjing',
items: [{
code: 'zhonghuamen',
name: 'Zhong Hua Men',
}],
}],
}];
const customerOptions = [
{
code: 'zhejiang',
name: 'Zhejiang',
items: [
{
code: 'hangzhou',
name: 'Hangzhou',
items: [
{
code: 'xihu',
name: 'West Lake',
},
],
},
],
},
{
code: 'jiangsu',
name: 'Jiangsu',
items: [
{
code: 'nanjing',
name: 'Nanjing',
items: [
{
code: 'zhonghuamen',
name: 'Zhong Hua Men',
},
],
},
],
},
];
const wrapper = mount(
<Cascader
options={customerOptions}
@ -263,12 +383,17 @@ describe('Cascader', () => {
label: 'name',
value: 'code',
}}
/>
/>,
);
wrapper.instance().handleChange(['zhejiang', 'hangzhou', 'xihu'], customerOptions);
expect(wrapper.find('.ant-cascader-picker-label').text().split('/').length).toBe(3);
expect(
wrapper
.find('.ant-cascader-picker-label')
.text()
.split('/').length,
).toBe(3);
expect(errorSpy).toHaveBeenLastCalledWith(
'Warning: `filedNames` of Cascader is a typo usage and deprecated, please use `fieldNames` instead.'
'Warning: `filedNames` of Cascader is a typo usage and deprecated, please use `fieldNames` instead.',
);
errorSpy.mockReset();
});
@ -300,7 +425,7 @@ describe('Cascader', () => {
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(wrapper.find('.ant-cascader-menu-item').length).toBe(2);
expect(errorSpy).toBeCalledWith(
'Warning: \'limit\' of showSearch in Cascader should be positive number or false.'
"Warning: 'limit' of showSearch in Cascader should be positive number or false.",
);
});
});

182
components/cascader/index.tsx

@ -40,7 +40,12 @@ export interface ShowSearchType {
prefixCls: string | undefined,
names: FilledFieldNamesType,
) => React.ReactNode;
sort?: (a: CascaderOptionType[], b: CascaderOptionType[], inputValue: string, names: FilledFieldNamesType) => number;
sort?: (
a: CascaderOptionType[],
b: CascaderOptionType[],
inputValue: string,
names: FilledFieldNamesType,
) => number;
matchInputWidth?: boolean;
limit?: number | false;
}
@ -104,14 +109,23 @@ export interface CascaderState {
const defaultLimit = 50;
function highlightKeyword(str: string, keyword: string, prefixCls: string | undefined) {
return str.split(keyword)
.map((node: string, index: number) => index === 0 ? node : [
<span className={`${prefixCls}-menu-item-keyword`} key="seperator">{keyword}</span>,
node,
]);
return str.split(keyword).map((node: string, index: number) =>
index === 0
? node
: [
<span className={`${prefixCls}-menu-item-keyword`} key="seperator">
{keyword}
</span>,
node,
],
);
}
function defaultFilterOption(inputValue: string, path: CascaderOptionType[], names: FilledFieldNamesType) {
function defaultFilterOption(
inputValue: string,
path: CascaderOptionType[],
names: FilledFieldNamesType,
) {
return path.some(option => (option[names.label] as string).indexOf(inputValue) > -1);
}
@ -123,14 +137,19 @@ function defaultRenderFilteredOption(
) {
return path.map((option, index) => {
const label = option[names.label];
const node = (label as string).indexOf(inputValue) > -1 ?
highlightKeyword(label as string, inputValue, prefixCls) : label;
const node =
(label as string).indexOf(inputValue) > -1
? highlightKeyword(label as string, inputValue, prefixCls)
: label;
return index === 0 ? node : [' / ', node];
});
}
function defaultSortFilteredOption(
a: CascaderOptionType[], b: CascaderOptionType[], inputValue: string, names: FilledFieldNamesType,
a: CascaderOptionType[],
b: CascaderOptionType[],
inputValue: string,
names: FilledFieldNamesType,
) {
function callback(elem: CascaderOptionType) {
return (elem[names.label] as string).indexOf(inputValue) > -1;
@ -183,8 +202,7 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
inputValue: '',
inputFocused: false,
popupVisible: props.popupVisible,
flattenOptions:
props.showSearch ? this.flattenTree(props.options, props) : undefined,
flattenOptions: props.showSearch ? this.flattenTree(props.options, props) : undefined,
};
}
@ -211,7 +229,7 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
return;
}
this.setValue(value, selectedOptions);
}
};
handlePopupVisibleChange = (popupVisible: boolean) => {
if (!('popupVisible' in this.props)) {
@ -226,13 +244,13 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
if (onPopupVisibleChange) {
onPopupVisibleChange(popupVisible);
}
}
};
handleInputBlur = () => {
this.setState({
inputFocused: false,
});
}
};
handleInputClick = (e: React.MouseEvent<HTMLInputElement>) => {
const { inputFocused, popupVisible } = this.state;
@ -243,18 +261,18 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
e.nativeEvent.stopImmediatePropagation();
}
}
}
};
handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.keyCode === KeyCode.BACKSPACE) {
e.stopPropagation();
}
}
};
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value;
this.setState({ inputValue });
}
};
setValue = (value: string[], selectedOptions: CascaderOptionType[] = []) => {
if (!('value' in this.props)) {
@ -264,14 +282,15 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
if (onChange) {
onChange(value, selectedOptions);
}
}
};
getLabel() {
const { options, displayRender = defaultDisplayRender as Function } = this.props;
const names = getFilledFieldNames(this.props);
const value = this.state.value;
const unwrappedValue = Array.isArray(value[0]) ? value[0] : value;
const selectedOptions: CascaderOptionType[] = arrayTreeFilter(options,
const selectedOptions: CascaderOptionType[] = arrayTreeFilter(
options,
(o: CascaderOptionType, level: number) => o[names.value] === unwrappedValue[level],
{ childrenKeyName: names.children },
);
@ -288,7 +307,7 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
} else {
this.setState({ inputValue: '' });
}
}
};
flattenTree(
options: CascaderOptionType[],
@ -298,19 +317,13 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
const names: FilledFieldNamesType = getFilledFieldNames(props);
let flattenOptions = [] as CascaderOptionType[][];
const childrenName = names.children;
options.forEach((option) => {
options.forEach(option => {
const path = ancestor.concat(option);
if (props.changeOnSelect || !option[childrenName] || !option[childrenName].length) {
flattenOptions.push(path);
}
if (option[childrenName]) {
flattenOptions = flattenOptions.concat(
this.flattenTree(
option[childrenName],
props,
path,
),
);
flattenOptions = flattenOptions.concat(this.flattenTree(option[childrenName], props, path));
}
});
return flattenOptions;
@ -334,7 +347,7 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
let matchCount = 0;
// Perf optimization to filter items only below the limit
flattenOptions.some((path) => {
flattenOptions.some(path => {
const match = filter(this.state.inputValue, path, names);
if (match) {
filtered.push(path);
@ -345,9 +358,9 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
} else {
warning(
typeof limit !== 'number',
'\'limit\' of showSearch in Cascader should be positive number or false.',
"'limit' of showSearch in Cascader should be positive number or false.",
);
filtered = flattenOptions.filter((path) => filter(this.state.inputValue, path, names));
filtered = flattenOptions.filter(path => filter(this.state.inputValue, path, names));
}
filtered.sort((a, b) => sort(a, b, inputValue, names));
@ -363,7 +376,9 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
} as CascaderOptionType;
});
}
return [{ [names.label]: notFoundContent, [names.value]: 'ANT_CASCADER_NOT_FOUND', disabled: true }];
return [
{ [names.label]: notFoundContent, [names.value]: 'ANT_CASCADER_NOT_FOUND', disabled: true },
];
}
focus() {
@ -376,13 +391,23 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
saveInput = (node: Input) => {
this.input = node;
}
};
renderCascader = ({ getPopupContainer: getContextPopupContainer }: ConfigProviderProps) => {
const { props, state } = this;
const {
prefixCls, inputPrefixCls, children, placeholder, size, disabled,
className, style, allowClear, showSearch = false, suffixIcon, ...otherProps
prefixCls,
inputPrefixCls,
children,
placeholder,
size,
disabled,
className,
style,
allowClear,
showSearch = false,
suffixIcon,
...otherProps
} = props;
const { value, inputFocused } = state;
@ -390,26 +415,26 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
[`${inputPrefixCls}-lg`]: size === 'large',
[`${inputPrefixCls}-sm`]: size === 'small',
});
const clearIcon = (allowClear && !disabled && value.length > 0) || state.inputValue ? (
<Icon
type="close-circle"
theme="filled"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
/>
) : null;
const clearIcon =
(allowClear && !disabled && value.length > 0) || state.inputValue ? (
<Icon
type="close-circle"
theme="filled"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
/>
) : null;
const arrowCls = classNames({
[`${prefixCls}-picker-arrow`]: true,
[`${prefixCls}-picker-arrow-expand`]: state.popupVisible,
});
const pickerCls = classNames(
className, `${prefixCls}-picker`, {
[`${prefixCls}-picker-with-value`]: state.inputValue,
[`${prefixCls}-picker-disabled`]: disabled,
[`${prefixCls}-picker-${size}`]: !!size,
[`${prefixCls}-picker-show-search`]: !!showSearch,
[`${prefixCls}-picker-focused`]: inputFocused,
});
const pickerCls = classNames(className, `${prefixCls}-picker`, {
[`${prefixCls}-picker-with-value`]: state.inputValue,
[`${prefixCls}-picker-disabled`]: disabled,
[`${prefixCls}-picker-${size}`]: !!size,
[`${prefixCls}-picker-show-search`]: !!showSearch,
[`${prefixCls}-picker-focused`]: inputFocused,
});
// Fix bug of https://github.com/facebook/react/pull/5004
// and https://fb.me/react-unknown-prop
@ -445,39 +470,34 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
this.cachedOptions = options;
}
const dropdownMenuColumnStyle: { width?: number, height?: string } = {};
const isNotFound = (options || []).length === 1 && options[0].value === 'ANT_CASCADER_NOT_FOUND';
const dropdownMenuColumnStyle: { width?: number; height?: string } = {};
const isNotFound =
(options || []).length === 1 && options[0].value === 'ANT_CASCADER_NOT_FOUND';
if (isNotFound) {
dropdownMenuColumnStyle.height = 'auto'; // Height of one row.
}
// The default value of `matchInputWidth` is `true`
const resultListMatchInputWidth = (showSearch as ShowSearchType).matchInputWidth === false ? false : true;
const resultListMatchInputWidth =
(showSearch as ShowSearchType).matchInputWidth === false ? false : true;
if (resultListMatchInputWidth && state.inputValue && this.input) {
dropdownMenuColumnStyle.width = this.input.input.offsetWidth;
}
const inputIcon = suffixIcon && (
React.isValidElement<{ className?: string }>(suffixIcon)
? React.cloneElement(
suffixIcon,
{
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-arrow`]: true,
}),
},
) : <span className={`${prefixCls}-picker-arrow`}>{suffixIcon}</span>) || (
<Icon type="down" className={arrowCls} />
);
const inputIcon = (suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
React.cloneElement(suffixIcon, {
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-arrow`]: true,
}),
})
) : (
<span className={`${prefixCls}-picker-arrow`}>{suffixIcon}</span>
))) || <Icon type="down" className={arrowCls} />;
const input = children || (
<span
style={style}
className={pickerCls}
>
<span className={`${prefixCls}-picker-label`}>
{this.getLabel()}
</span>
<span style={style} className={pickerCls}>
<span className={`${prefixCls}-picker-label`}>{this.getLabel()}</span>
<Input
{...inputProps}
ref={this.saveInput}
@ -498,9 +518,7 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
</span>
);
const expandIcon = (
<Icon type="right" />
);
const expandIcon = <Icon type="right" />;
const loadingIcon = (
<span className={`${prefixCls}-menu-item-loading-icon`}>
@ -527,13 +545,9 @@ export default class Cascader extends React.Component<CascaderProps, CascaderSta
{input}
</RcCascader>
);
}
};
render() {
return (
<ConfigConsumer>
{this.renderCascader}
</ConfigConsumer>
);
return <ConfigConsumer>{this.renderCascader}</ConfigConsumer>;
}
}

12
components/cascader/style/index.less

@ -1,8 +1,8 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "../../input/style/mixin";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@cascader-prefix-cls: ~"@{ant-prefix}-cascader";
@cascader-prefix-cls: ~'@{ant-prefix}-cascader';
.@{cascader-prefix-cls} {
.reset-component;
@ -28,7 +28,7 @@
background-color: @component-background;
border-radius: @border-radius-base;
outline: 0;
transition: color .3s;
transition: color 0.3s;
&-with-value &-label {
color: transparent;
@ -101,7 +101,7 @@
margin-top: -6px;
line-height: 12px;
color: @disabled-color;
transition: transform .2s;
transition: transform 0.2s;
&&-expand {
transform: rotate(180deg);
}

16
components/checkbox/Checkbox.tsx

@ -53,10 +53,16 @@ export default class Checkbox extends React.Component<CheckboxProps, {}, {}> {
private rcCheckbox: any;
shouldComponentUpdate(nextProps: CheckboxProps, nextState: {}, nextContext: CheckboxGroupContext) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState) ||
!shallowEqual(this.context.checkboxGroup, nextContext.checkboxGroup);
shouldComponentUpdate(
nextProps: CheckboxProps,
nextState: {},
nextContext: CheckboxGroupContext,
) {
return (
!shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState) ||
!shallowEqual(this.context.checkboxGroup, nextContext.checkboxGroup)
);
}
focus() {
@ -69,7 +75,7 @@ export default class Checkbox extends React.Component<CheckboxProps, {}, {}> {
saveCheckbox = (node: any) => {
this.rcCheckbox = node;
}
};
render() {
const { props, context } = this;

9
components/checkbox/Group.tsx

@ -71,7 +71,7 @@ class CheckboxGroup extends React.Component<CheckboxGroupProps, CheckboxGroupSta
super(props);
this.state = {
value: props.value || props.defaultValue || [],
};
};
}
getChildContext() {
@ -85,8 +85,7 @@ class CheckboxGroup extends React.Component<CheckboxGroupProps, CheckboxGroupSta
}
shouldComponentUpdate(nextProps: CheckboxGroupProps, nextState: CheckboxGroupState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}
getOptions() {
@ -106,7 +105,7 @@ class CheckboxGroup extends React.Component<CheckboxGroupProps, CheckboxGroupSta
toggleOption = (option: CheckboxOptionType) => {
const optionIndex = this.state.value.indexOf(option.value);
const value = [...this.state.value];
if (optionIndex === - 1) {
if (optionIndex === -1) {
value.push(option.value);
} else {
value.splice(optionIndex, 1);
@ -118,7 +117,7 @@ class CheckboxGroup extends React.Component<CheckboxGroupProps, CheckboxGroupSta
if (onChange) {
onChange(value);
}
}
};
render() {
const { props, state } = this;

7
components/checkbox/__tests__/checkbox.test.js

@ -10,12 +10,7 @@ describe('Checkbox', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = shallow(
<Checkbox
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
const wrapper = shallow(<Checkbox onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />);
wrapper.simulate('mouseenter');
expect(onMouseEnter).toHaveBeenCalled();

78
components/checkbox/__tests__/group.test.js

@ -6,32 +6,47 @@ describe('CheckboxGroup', () => {
it('should work basically', () => {
const onChange = jest.fn();
const wrapper = mount(
<Checkbox.Group options={['Apple', 'Pear', 'Orange']} onChange={onChange} />
<Checkbox.Group options={['Apple', 'Pear', 'Orange']} onChange={onChange} />,
);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
expect(onChange).toBeCalledWith(['Apple']);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
wrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
expect(onChange).toBeCalledWith(['Apple', 'Pear']);
wrapper.find('.ant-checkbox-input').at(2).simulate('change');
wrapper
.find('.ant-checkbox-input')
.at(2)
.simulate('change');
expect(onChange).toBeCalledWith(['Apple', 'Pear', 'Orange']);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
wrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
expect(onChange).toBeCalledWith(['Apple', 'Orange']);
});
it('does not trigger onChange callback of both Checkbox and CheckboxGroup when CheckboxGroup is disabled', () => {
const onChangeGroup = jest.fn();
const options = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
];
const options = [{ label: 'Apple', value: 'Apple' }, { label: 'Pear', value: 'Pear' }];
const groupWrapper = mount(
<Checkbox.Group options={options} onChange={onChangeGroup} disabled />
<Checkbox.Group options={options} onChange={onChangeGroup} disabled />,
);
groupWrapper.find('.ant-checkbox-input').at(0).simulate('change');
groupWrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
expect(onChangeGroup).not.toBeCalled();
groupWrapper.find('.ant-checkbox-input').at(1).simulate('change');
groupWrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
expect(onChangeGroup).not.toBeCalled();
});
@ -43,37 +58,31 @@ describe('CheckboxGroup', () => {
{ label: 'Orange', value: 'Orange', disabled: true },
];
const groupWrapper = mount(
<Checkbox.Group options={options} onChange={onChangeGroup} />
);
groupWrapper.find('.ant-checkbox-input').at(0).simulate('change');
const groupWrapper = mount(<Checkbox.Group options={options} onChange={onChangeGroup} />);
groupWrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
expect(onChangeGroup).toBeCalledWith(['Apple']);
groupWrapper.find('.ant-checkbox-input').at(1).simulate('change');
groupWrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
expect(onChangeGroup).toBeCalledWith(['Apple']);
});
it('passes prefixCls down to checkbox', () => {
const options = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Orange', value: 'Orange' },
];
const options = [{ label: 'Apple', value: 'Apple' }, { label: 'Orange', value: 'Orange' }];
const wrapper = render(
<Checkbox.Group prefixCls="my-checkbox" options={options} />
);
const wrapper = render(<Checkbox.Group prefixCls="my-checkbox" options={options} />);
expect(wrapper).toMatchSnapshot();
});
it('should be controlled by value', () => {
const options = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Orange', value: 'Orange' },
];
const options = [{ label: 'Apple', value: 'Apple' }, { label: 'Orange', value: 'Orange' }];
const wrapper = mount(
<Checkbox.Group options={options} />
);
const wrapper = mount(<Checkbox.Group options={options} />);
expect(wrapper.instance().state.value).toEqual([]);
wrapper.setProps({ value: ['Apple'] });
@ -86,9 +95,12 @@ describe('CheckboxGroup', () => {
const wrapper = mount(
<Checkbox.Group>
<Checkbox value="my" onChange={onChange} />
</Checkbox.Group>
</Checkbox.Group>,
);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
expect(onChange).toBeCalled();
expect(onChange.mock.calls[0][0].target.value).toEqual('my');
});

4
components/checkbox/style/index.less

@ -1,4 +1,4 @@
@import "../../style/themes/default";
@import "./mixin";
@import '../../style/themes/default';
@import './mixin';
.antCheckboxFn();

14
components/checkbox/style/mixin.less

@ -1,7 +1,7 @@
@import "../../style/mixins/index";
@import '../../style/mixins/index';
.antCheckboxFn(@checkbox-prefix-cls: ~"@{ant-prefix}-checkbox") {
@checkbox-inner-prefix-cls: ~"@{checkbox-prefix-cls}-inner";
.antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-checkbox') {
@checkbox-inner-prefix-cls: ~'@{checkbox-prefix-cls}-inner';
// 一般状态
.@{checkbox-prefix-cls} {
.reset-component;
@ -28,7 +28,7 @@
height: 100%;
border-radius: @border-radius-sm;
border: 1px solid @checkbox-color;
content: "";
content: '';
animation: antCheckboxEffect 0.36s ease-in-out;
animation-fill-mode: both;
visibility: hidden;
@ -49,7 +49,7 @@
border: @checkbox-border-width @border-style-base @border-color-base;
border-radius: @border-radius-sm;
background-color: @checkbox-check-color;
transition: all .3s;
transition: all 0.3s;
// Fix IE checked style
// https://github.com/ant-design/ant-design/issues/12597
border-collapse: separate;
@ -68,7 +68,7 @@
border-top: 0;
border-left: 0;
content: ' ';
transition: all .1s @ease-in-back, opacity .1s;
transition: all 0.1s @ease-in-back, opacity 0.1s;
opacity: 0;
}
}
@ -96,7 +96,7 @@
border-top: 0;
border-left: 0;
content: ' ';
transition: all .2s @ease-out-back .1s;
transition: all 0.2s @ease-out-back 0.1s;
opacity: 1;
}

17
components/collapse/Collapse.tsx

@ -24,20 +24,21 @@ export default class Collapse extends React.Component<CollapseProps, any> {
static defaultProps = {
prefixCls: 'ant-collapse',
bordered: true,
openAnimation: { ...animation, appear() { } },
openAnimation: { ...animation, appear() {} },
};
renderExpandIcon = () => {
return (
<Icon type="right" className={`arrow`} />
);
}
return <Icon type="right" className={`arrow`} />;
};
render() {
const { prefixCls, className = '', bordered } = this.props;
const collapseClassName = classNames({
[`${prefixCls}-borderless`]: !bordered,
}, className);
const collapseClassName = classNames(
{
[`${prefixCls}-borderless`]: !bordered,
},
className,
);
return (
<RcCollapse
{...this.props}

9
components/collapse/CollapsePanel.tsx

@ -17,9 +17,12 @@ export interface CollapsePanelProps {
export default class CollapsePanel extends React.Component<CollapsePanelProps, {}> {
render() {
const { prefixCls, className = '', showArrow = true } = this.props;
const collapsePanelClassName = classNames({
[`${prefixCls}-no-arrow`]: !showArrow,
}, className);
const collapsePanelClassName = classNames(
{
[`${prefixCls}-no-arrow`]: !showArrow,
},
className,
);
return <RcCollapse.Panel {...this.props} className={collapsePanelClassName} />;
}
}

12
components/collapse/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@collapse-prefix-cls: ~"@{ant-prefix}-collapse";
@collapse-prefix-cls: ~'@{ant-prefix}-collapse';
.collapse-close() {
transform: rotate(0);
@ -33,7 +33,7 @@
color: @heading-color;
cursor: pointer;
position: relative;
transition: all .3s;
transition: all 0.3s;
.arrow {
.iconfont-mixin();
@ -64,7 +64,7 @@
}
&-anim-active {
transition: height .2s @ease-out;
transition: height 0.2s @ease-out;
}
&-content {
@ -88,7 +88,7 @@
}
}
& > &-item > &-header[aria-expanded="true"] {
& > &-item > &-header[aria-expanded='true'] {
.anticon-right svg {
.collapse-open();
}

40
components/comment/index.tsx

@ -25,30 +25,21 @@ export interface CommentProps {
export default class Comment extends React.Component<CommentProps, {}> {
static defaultProps = {
prefixCls: 'ant-comment',
}
};
getAction(actions: React.ReactNode[]) {
if (!actions || !actions.length) {
return null;
}
const actionList = actions.map((action, index) => (
<li key={`action-${index}`}>
{action}
</li>
),
);
const actionList = actions.map((action, index) => <li key={`action-${index}`}>{action}</li>);
return actionList;
}
renderNested = (children: any) => {
const { prefixCls } = this.props;
return (
<div className={classNames(`${prefixCls}-nested`)}>
{children}
</div>
);
}
return <div className={classNames(`${prefixCls}-nested`)}>{children}</div>;
};
render() {
const {
@ -70,31 +61,22 @@ export default class Comment extends React.Component<CommentProps, {}> {
</div>
);
const actionDom = actions && actions.length
? <ul className={`${prefixCls}-actions`}>{this.getAction(actions)}</ul>
: null;
const actionDom =
actions && actions.length ? (
<ul className={`${prefixCls}-actions`}>{this.getAction(actions)}</ul>
) : null;
const authorContent = (
<div className={`${prefixCls}-content-author`}>
{author && (
<span className={`${prefixCls}-content-author-name`}>
{author}
</span>
)}
{datetime && (
<span className={`${prefixCls}-content-author-time`}>
{datetime}
</span>
)}
{author && <span className={`${prefixCls}-content-author-name`}>{author}</span>}
{datetime && <span className={`${prefixCls}-content-author-time`}>{datetime}</span>}
</div>
);
const contentDom = (
<div className={`${prefixCls}-content`}>
{authorContent}
<div className={`${prefixCls}-content-detail`}>
{content}
</div>
<div className={`${prefixCls}-content-detail`}>{content}</div>
{actionDom}
</div>
);

10
components/comment/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@comment-prefix-cls: ~"@{ant-prefix}-comment";
@comment-prefix-cls: ~'@{ant-prefix}-comment';
.@{comment-prefix-cls} {
position: relative;
@ -44,7 +44,7 @@
}
&-name {
transition: color .3s;
transition: color 0.3s;
font-size: 14px;
color: @comment-author-name-color;
> * {
@ -75,7 +75,7 @@
color: @comment-action-color;
> span {
padding-right: 10px;
transition: color .3s;
transition: color 0.3s;
font-size: 12px;
color: @comment-action-color;
cursor: pointer;

14
components/config-provider/index.tsx

@ -7,19 +7,15 @@ export interface ConfigProviderProps {
const ConfigContext: Context<ConfigProviderProps | null> = createReactContext({});
const ConfigProvider: React.SFC<ConfigProviderProps> = (props) => {
const ConfigProvider: React.SFC<ConfigProviderProps> = props => {
const { getPopupContainer, children } = props;
const config = {
getPopupContainer,
};
return (
<ConfigContext.Provider value={config}>
{children}
</ConfigContext.Provider>
);
}
return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;
};
export const ConfigConsumer = ConfigContext.Consumer;
export const ConfigConsumer = ConfigContext.Consumer;
export default ConfigProvider;
export default ConfigProvider;

2
components/config-provider/style/index.less

@ -1,2 +1,2 @@
// placeholder
@import "../../style/themes/default";
@import '../../style/themes/default';

131
components/date-picker/RangePicker.tsx

@ -20,7 +20,7 @@ export interface RangePickerState {
}
function getShowDateFromValue(value: RangePickerValue) {
const [ start, end ] = value;
const [start, end] = value;
// value could be an empty array, then we should not reset showDate
if (!start && !end) {
return;
@ -33,7 +33,9 @@ function formatValue(value: moment.Moment | undefined, format: string): string {
return (value && value.format(format)) || '';
}
function pickerValueAdapter(value?: moment.Moment | RangePickerValue): RangePickerValue | undefined {
function pickerValueAdapter(
value?: moment.Moment | RangePickerValue,
): RangePickerValue | undefined {
if (!value) {
return;
}
@ -57,7 +59,7 @@ function fixLocale(value: RangePickerValue | undefined, localeCode: string) {
if (!value || value.length === 0) {
return;
}
const [ start, end ] = value;
const [start, end] = value;
if (start) {
start!.locale(localeCode);
}
@ -88,7 +90,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
};
}
}
if (('open' in nextProps) && prevState.open !== nextProps.open) {
if ('open' in nextProps && prevState.open !== nextProps.open) {
state = {
...state,
open: nextProps.open,
@ -102,14 +104,14 @@ class RangePicker extends React.Component<any, RangePickerState> {
constructor(props: any) {
super(props);
const value = props.value || props.defaultValue || [];
const [ start, end ] = value;
const [start, end] = value;
if (
start && !interopDefault(moment).isMoment(start) ||
end && !interopDefault(moment).isMoment(end)
(start && !interopDefault(moment).isMoment(start)) ||
(end && !interopDefault(moment).isMoment(end))
) {
throw new Error(
'The value/defaultValue of RangePicker must be a moment object array after `antd@2.0`, ' +
'see: https://u.ant.design/date-picker-value',
'see: https://u.ant.design/date-picker-value',
);
}
const pickerValue = !value || isEmptyArray(value) ? props.defaultPickerValue : value;
@ -126,7 +128,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
e.stopPropagation();
this.setState({ value: [] });
this.handleChange([]);
}
};
clearHoverValue = () => this.setState({ hoverValue: [] });
@ -138,12 +140,9 @@ class RangePicker extends React.Component<any, RangePickerState> {
showDate: getShowDateFromValue(value) || showDate,
}));
}
const [ start, end ] = value;
props.onChange(value, [
formatValue(start, props.format),
formatValue(end, props.format),
]);
}
const [start, end] = value;
props.onChange(value, [formatValue(start, props.format), formatValue(end, props.format)]);
};
handleOpenChange = (open: boolean) => {
if (!('open' in this.props)) {
@ -162,7 +161,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
if (!open) {
this.focus();
}
}
};
handleShowDateChange = (showDate: RangePickerValue) => this.setState({ showDate });
@ -172,10 +171,10 @@ class RangePicker extends React.Component<any, RangePickerState> {
if (this.state.open) {
this.clearHoverValue();
}
}
};
handleCalendarInputSelect = (value: RangePickerValue) => {
const [ start ] = value;
const [start] = value;
if (!start) {
return;
}
@ -183,7 +182,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
value,
showDate: getShowDateFromValue(value) || showDate,
}));
}
};
handleRangeClick = (value: RangePickerPresetRange) => {
if (typeof value === 'function') {
@ -200,7 +199,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
if (onOpenChange) {
onOpenChange(false);
}
}
};
setValue(value: RangePickerValue, hidePanel?: boolean) {
this.handleChange(value);
@ -219,7 +218,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
savePicker = (node: HTMLSpanElement) => {
this.picker = node;
}
};
renderFooter = (...args: any[]) => {
const { prefixCls, ranges, renderExtraFooter, tagPrefixCls } = this.props;
@ -231,7 +230,7 @@ class RangePicker extends React.Component<any, RangePickerState> {
{renderExtraFooter(...args)}
</div>
) : null;
const operations = Object.keys(ranges || {}).map((range) => {
const operations = Object.keys(ranges || {}).map(range => {
const value = ranges[range];
return (
<Tag
@ -246,23 +245,34 @@ class RangePicker extends React.Component<any, RangePickerState> {
</Tag>
);
});
const rangeNode = (operations && operations.length > 0) ? (
<div className={`${prefixCls}-footer-extra ${prefixCls}-range-quick-selector`} key="range">
{operations}
</div>
) : null;
const rangeNode =
operations && operations.length > 0 ? (
<div className={`${prefixCls}-footer-extra ${prefixCls}-range-quick-selector`} key="range">
{operations}
</div>
) : null;
return [rangeNode, customFooter];
}
};
render() {
const { state, props } = this;
const { value, showDate, hoverValue, open } = state;
const {
prefixCls, popupStyle, style,
disabledDate, disabledTime,
showTime, showToday,
ranges, onOk, locale, localeCode, format,
dateRender, onCalendarChange, suffixIcon,
prefixCls,
popupStyle,
style,
disabledDate,
disabledTime,
showTime,
showToday,
ranges,
onOk,
locale,
localeCode,
format,
dateRender,
onCalendarChange,
suffixIcon,
} = props;
fixLocale(value, localeCode);
@ -291,10 +301,10 @@ class RangePicker extends React.Component<any, RangePickerState> {
calendarProps.mode = props.mode;
}
const startPlaceholder = ('placeholder' in props)
? props.placeholder[0] : locale.lang.rangePlaceholder[0];
const endPlaceholder = ('placeholder' in props)
? props.placeholder[1] : locale.lang.rangePlaceholder[1];
const startPlaceholder =
'placeholder' in props ? props.placeholder[0] : locale.lang.rangePlaceholder[0];
const endPlaceholder =
'placeholder' in props ? props.placeholder[1] : locale.lang.rangePlaceholder[1];
const calendar = (
<RangeCalendar
@ -326,32 +336,31 @@ class RangePicker extends React.Component<any, RangePickerState> {
if (props.showTime) {
pickerStyle.width = (style && style.width) || 350;
}
const [ startValue, endValue ] = value as RangePickerValue;
const clearIcon = (!props.disabled && props.allowClear && value && (startValue || endValue)) ? (
<Icon
type="close-circle"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
theme="filled"
/>
) : null;
const inputIcon = suffixIcon && (
React.isValidElement<{ className?: string }>(suffixIcon)
? React.cloneElement(
suffixIcon,
{
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
},
) : <span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>) || (
<Icon type="calendar" className={`${prefixCls}-picker-icon`} />
);
const [startValue, endValue] = value as RangePickerValue;
const clearIcon =
!props.disabled && props.allowClear && value && (startValue || endValue) ? (
<Icon
type="close-circle"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
theme="filled"
/>
) : null;
const inputIcon = (suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
React.cloneElement(suffixIcon, {
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
})
) : (
<span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>
))) || <Icon type="calendar" className={`${prefixCls}-picker-icon`} />;
const input = ({ value: inputValue }: { value: any }) => {
const [ start, end ] = inputValue;
const [start, end] = inputValue;
return (
<span className={props.pickerInputClass}>
<input

98
components/date-picker/WeekPicker.tsx

@ -44,7 +44,7 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
if (value && !interopDefault(moment).isMoment(value)) {
throw new Error(
'The value/defaultValue of DatePicker or MonthPicker must be ' +
'a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value',
'a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value',
);
}
this.state = {
@ -56,30 +56,26 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
weekDateRender = (current: any) => {
const selectedValue = this.state.value;
const { prefixCls } = this.props;
if (selectedValue &&
if (
selectedValue &&
current.year() === selectedValue.year() &&
current.week() === selectedValue.week()) {
current.week() === selectedValue.week()
) {
return (
<div className={`${prefixCls}-selected-day`}>
<div className={`${prefixCls}-date`}>
{current.date()}
</div>
<div className={`${prefixCls}-date`}>{current.date()}</div>
</div>
);
}
return (
<div className={`${prefixCls}-date`}>
{current.date()}
</div>
);
}
return <div className={`${prefixCls}-date`}>{current.date()}</div>;
};
handleChange = (value: moment.Moment | null) => {
if (!('value' in this.props)) {
this.setState({ value });
}
this.props.onChange(value, formatValue(value, this.props.format));
}
};
handleOpenChange = (open: boolean) => {
const { onOpenChange } = this.props;
@ -100,7 +96,7 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
e.preventDefault();
e.stopPropagation();
this.handleChange(null);
}
};
focus() {
this.input.focus();
@ -112,13 +108,26 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
saveInput = (node: any) => {
this.input = node;
}
};
render() {
const {
prefixCls, className, disabled, pickerClass, popupStyle,
pickerInputClass, format, allowClear, locale, localeCode, disabledDate,
style, onFocus, onBlur, id, suffixIcon,
prefixCls,
className,
disabled,
pickerClass,
popupStyle,
pickerInputClass,
format,
allowClear,
locale,
localeCode,
disabledDate,
style,
onFocus,
onBlur,
id,
suffixIcon,
} = this.props;
const { open } = this.state;
@ -127,8 +136,8 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
pickerValue.locale(localeCode);
}
const placeholder = ('placeholder' in this.props)
? this.props.placeholder : locale.lang.placeholder;
const placeholder =
'placeholder' in this.props ? this.props.placeholder : locale.lang.placeholder;
const calendar = (
<Calendar
@ -142,28 +151,27 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
disabledDate={disabledDate}
/>
);
const clearIcon = (!disabled && allowClear && this.state.value) ? (
<Icon
type="close-circle"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
theme="filled"
/>
) : null;
const inputIcon = suffixIcon && (
React.isValidElement<{ className?: string }>(suffixIcon)
? React.cloneElement(
suffixIcon,
{
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
},
) : <span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>) || (
<Icon type="calendar" className={`${prefixCls}-picker-icon`} />
);
const clearIcon =
!disabled && allowClear && this.state.value ? (
<Icon
type="close-circle"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
theme="filled"
/>
) : null;
const inputIcon = (suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
React.cloneElement(suffixIcon, {
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
})
) : (
<span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>
))) || <Icon type="calendar" className={`${prefixCls}-picker-icon`} />;
const input = ({ value }: { value: moment.Moment | undefined }) => {
return (
@ -184,11 +192,7 @@ class WeekPicker extends React.Component<any, WeekPickerState> {
);
};
return (
<span
className={classNames(className, pickerClass)}
style={style}
id={id}
>
<span className={classNames(className, pickerClass)} style={style} id={id}>
<RcDatePicker
{...this.props}
calendar={calendar}

52
components/date-picker/__tests__/DatePicker.test.js

@ -3,14 +3,7 @@ import { mount } from 'enzyme';
import moment from 'moment';
import MockDate from 'mockdate';
import DatePicker from '..';
import {
selectDate,
openPanel,
clearInput,
nextYear,
nextMonth,
hasSelected,
} from './utils';
import { selectDate, openPanel, clearInput, nextYear, nextMonth, hasSelected } from './utils';
import focusTest from '../../../tests/shared/focusTest';
describe('DatePicker', () => {
@ -28,10 +21,7 @@ describe('DatePicker', () => {
const locale = {
lang: {
placeholder: 'Избери дата',
rangePlaceholder: [
'Начална дата',
'Крайна дата',
],
rangePlaceholder: ['Начална дата', 'Крайна дата'],
today: 'Днес',
now: 'Сега',
backToToday: 'Към днес',
@ -63,9 +53,7 @@ describe('DatePicker', () => {
},
};
const birthday = moment('2000-01-01', 'YYYY-MM-DD');
const wrapper = mount(
<DatePicker open locale={locale} value={birthday} />
);
const wrapper = mount(<DatePicker open locale={locale} value={birthday} />);
expect(wrapper.render()).toMatchSnapshot();
});
@ -75,9 +63,9 @@ describe('DatePicker', () => {
state = {
cleared: false,
value: moment(),
}
};
onChange = (value) => {
onChange = value => {
let { cleared } = this.state;
let newValue = value;
@ -91,7 +79,7 @@ describe('DatePicker', () => {
}
this.setState({ value: newValue, cleared });
}
};
render() {
const { value } = this.state;
@ -118,9 +106,7 @@ describe('DatePicker', () => {
it('triggers onChange only when date was selected', () => {
const handleChange = jest.fn();
const wrapper = mount(
<DatePicker onChange={handleChange} />
);
const wrapper = mount(<DatePicker onChange={handleChange} />);
openPanel(wrapper);
nextYear(wrapper);
expect(handleChange).not.toBeCalled();
@ -131,9 +117,7 @@ describe('DatePicker', () => {
});
it('clear input', () => {
const wrapper = mount(
<DatePicker />
);
const wrapper = mount(<DatePicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'));
clearInput(wrapper);
@ -142,35 +126,27 @@ describe('DatePicker', () => {
});
it('sets data attributes on input', () => {
const wrapper = mount(
<DatePicker data-test="test-id" data-id="12345" />
);
const wrapper = mount(<DatePicker data-test="test-id" data-id="12345" />);
const input = wrapper.find('.ant-calendar-picker-input').getDOMNode();
expect(input.getAttribute('data-test')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
});
it('sets aria attributes on input', () => {
const wrapper = mount(
<DatePicker aria-label="some-label" aria-labelledby="label-id" />
);
const wrapper = mount(<DatePicker aria-label="some-label" aria-labelledby="label-id" />);
const input = wrapper.find('.ant-calendar-picker-input').getDOMNode();
expect(input.getAttribute('aria-label')).toBe('some-label');
expect(input.getAttribute('aria-labelledby')).toBe('label-id');
});
it('sets role attribute on input', () => {
const wrapper = mount(
<DatePicker role="search" />
);
const wrapper = mount(<DatePicker role="search" />);
const input = wrapper.find('.ant-calendar-picker-input').getDOMNode();
expect(input.getAttribute('role')).toBe('search');
});
it('changes year/month when under control', () => {
const wrapper = mount(
<DatePicker value={moment('2018-07-01')} />
);
const wrapper = mount(<DatePicker value={moment('2018-07-01')} />);
openPanel(wrapper);
expect(wrapper.find('.ant-calendar-my-select').text()).toBe('Jul2018');
wrapper.find('.ant-calendar-prev-year-btn').simulate('click');
@ -183,9 +159,7 @@ describe('DatePicker', () => {
return current && current < moment().endOf('day');
}
const wrapper = mount(
<DatePicker disabledDate={disabledDate} />
);
const wrapper = mount(<DatePicker disabledDate={disabledDate} />);
expect(wrapper.render()).toMatchSnapshot();
});

14
components/date-picker/__tests__/MonthPicker.test.js

@ -11,11 +11,15 @@ describe('MonthPicker', () => {
focusTest(MonthPicker);
it('reset select item when popup close', () => {
const wrapper = mount(
<MonthPicker value={moment('2018-07-01')} />
);
const wrapper = mount(<MonthPicker value={moment('2018-07-01')} />);
openPanel(wrapper);
wrapper.find('.ant-calendar-month-panel-month').first().simulate('click');
wrapper.find('.ant-calendar-month-panel-cell').at(6).hasClass('ant-calendar-month-panel-selected-cell');
wrapper
.find('.ant-calendar-month-panel-month')
.first()
.simulate('click');
wrapper
.find('.ant-calendar-month-panel-cell')
.at(6)
.hasClass('ant-calendar-month-panel-selected-cell');
});
});

226
components/date-picker/__tests__/RangePicker.test.js

@ -22,17 +22,18 @@ describe('RangePicker', () => {
it('show month panel according to value', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD').locale('zh-cn');
const wrapper = mount(
<RangePicker
getCalendarContainer={trigger => trigger}
format="YYYY/MM/DD"
showTime
open
/>
<RangePicker getCalendarContainer={trigger => trigger} format="YYYY/MM/DD" showTime open />,
);
wrapper.setProps({ value: [birthday, birthday] });
expect(render(wrapper.find('Trigger').instance().getComponent()))
.toMatchSnapshot();
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('switch to corresponding month panel when click presetted ranges', () => {
@ -46,14 +47,24 @@ describe('RangePicker', () => {
format="YYYY/MM/DD"
showTime
open
/>
/>,
);
const rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
rangeCalendarWrapper.find('.ant-calendar-range-quick-selector Tag')
.simulate('click');
expect(render(wrapper.find('Trigger').instance().getComponent()))
.toMatchSnapshot();
const rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
rangeCalendarWrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('highlight range when hover presetted range', () => {
@ -65,13 +76,22 @@ describe('RangePicker', () => {
getCalendarContainer={trigger => trigger}
format="YYYY/MM/DD"
open
/>
/>,
);
let rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
rangeCalendarWrapper.find('.ant-calendar-range-quick-selector Tag')
.simulate('mouseEnter');
rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
let rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
rangeCalendarWrapper.find('.ant-calendar-range-quick-selector Tag').simulate('mouseEnter');
rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(rangeCalendarWrapper.find('.ant-calendar-selected-day').length).toBe(2);
});
@ -82,10 +102,18 @@ describe('RangePicker', () => {
getCalendarContainer={trigger => trigger}
onCalendarChange={onCalendarChangeFn}
open
/>
/>,
);
const rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
const rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
rangeCalendarWrapper.find('.ant-calendar-cell').at(15).simulate('click');
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click');
expect(onCalendarChangeFn).toHaveBeenCalled();
});
@ -93,34 +121,57 @@ describe('RangePicker', () => {
it('should not throw error when value is reset to `[]`', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD');
const wrapper = mount(
<RangePicker
getCalendarContainer={trigger => trigger}
value={[birthday, birthday]}
open
/>
<RangePicker getCalendarContainer={trigger => trigger} value={[birthday, birthday]} open />,
);
wrapper.setProps({ value: [] });
const rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(() => rangeCalendarWrapper.find('.ant-calendar-cell').at(15).simulate('click').simulate('click'))
.not.toThrow();
const rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(() =>
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click')
.simulate('click'),
).not.toThrow();
});
// issue: https://github.com/ant-design/ant-design/issues/7077
it('should not throw error when select after clear', () => {
const wrapper = mount(
<RangePicker
getCalendarContainer={trigger => trigger}
open
/>
const wrapper = mount(<RangePicker getCalendarContainer={trigger => trigger} open />);
let rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
let rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
rangeCalendarWrapper.find('.ant-calendar-cell').at(15).simulate('click').simulate('click');
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click')
.simulate('click');
wrapper.update();
wrapper.find('.ant-calendar-picker-clear').hostNodes().simulate('click');
wrapper
.find('.ant-calendar-picker-clear')
.hostNodes()
.simulate('click');
wrapper.find('.ant-calendar-picker-input').simulate('click');
rangeCalendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(() => rangeCalendarWrapper.find('.ant-calendar-cell').at(15).simulate('click').simulate('click'))
.not.toThrow();
rangeCalendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(() =>
rangeCalendarWrapper
.find('.ant-calendar-cell')
.at(15)
.simulate('click')
.simulate('click'),
).not.toThrow();
});
it('clear hover value after panel close', () => {
@ -128,16 +179,25 @@ describe('RangePicker', () => {
const wrapper = mount(
<div>
<RangePicker value={[moment(), moment().add(2, 'day')]} />
</div>
</div>,
);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-cell').at(25).simulate('click');
wrapper.find('.ant-calendar-cell').at(27).simulate('mouseEnter');
wrapper
.find('.ant-calendar-cell')
.at(25)
.simulate('click');
wrapper
.find('.ant-calendar-cell')
.at(27)
.simulate('mouseEnter');
document.dispatchEvent(new MouseEvent('mousedown'));
jest.runAllTimers();
wrapper.find('.ant-calendar-picker-input').simulate('click');
expect(
wrapper.find('.ant-calendar-cell').at(23).hasClass('ant-calendar-in-range-cell')
wrapper
.find('.ant-calendar-cell')
.at(23)
.hasClass('ant-calendar-in-range-cell'),
).toBe(true);
});
@ -145,19 +205,20 @@ describe('RangePicker', () => {
it('static range', () => {
const range = [moment().subtract(2, 'd'), moment()];
const format = 'YYYY-MM-DD HH:mm:ss';
const wrapper = mount(
<RangePicker
ranges={{ 'recent two days': range }}
format={format}
/>
);
const wrapper = mount(<RangePicker ranges={{ 'recent two days': range }} format={format} />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(
wrapper.find('.ant-calendar-range-picker-input').first().getDOMNode().value
wrapper
.find('.ant-calendar-range-picker-input')
.first()
.getDOMNode().value,
).toBe(range[0].format(format));
expect(
wrapper.find('.ant-calendar-range-picker-input').last().getDOMNode().value
wrapper
.find('.ant-calendar-range-picker-input')
.last()
.getDOMNode().value,
).toBe(range[1].format(format));
});
@ -165,18 +226,21 @@ describe('RangePicker', () => {
const range = [moment().subtract(2, 'd'), moment()];
const format = 'YYYY-MM-DD HH:mm:ss';
const wrapper = mount(
<RangePicker
ranges={{ 'recent two days': () => range }}
format={format}
/>
<RangePicker ranges={{ 'recent two days': () => range }} format={format} />,
);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(
wrapper.find('.ant-calendar-range-picker-input').first().getDOMNode().value
wrapper
.find('.ant-calendar-range-picker-input')
.first()
.getDOMNode().value,
).toBe(range[0].format(format));
expect(
wrapper.find('.ant-calendar-range-picker-input').last().getDOMNode().value
wrapper
.find('.ant-calendar-range-picker-input')
.last()
.getDOMNode().value,
).toBe(range[1].format(format));
});
});
@ -193,12 +257,7 @@ describe('RangePicker', () => {
it('triggers onOk when click on preset range', () => {
const handleOk = jest.fn();
const range = [moment().subtract(2, 'd'), moment()];
const wrapper = mount(
<RangePicker
ranges={{ 'recent two days': range }}
onOk={handleOk}
/>
);
const wrapper = mount(<RangePicker ranges={{ 'recent two days': range }} onOk={handleOk} />);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(handleOk).toBeCalledWith(range);
@ -211,25 +270,46 @@ describe('RangePicker', () => {
selectDate(wrapper, moment('2017-09-18'), 0);
selectDate(wrapper, moment('2017-10-18'), 1);
wrapper.find('.ant-calendar-picker-input').simulate('click');
expect(() => (
wrapper.find('.ant-calendar-input').at(1).simulate('change', { target: { value: '2016-01-01' } })
)).not.toThrow();
expect(() =>
wrapper
.find('.ant-calendar-input')
.at(1)
.simulate('change', { target: { value: '2016-01-01' } }),
).not.toThrow();
});
it('changes year/month when under control', () => {
const wrapper = mount(<RangePicker value={[moment('2018-07-01'), moment('2018-07-02')]} />);
openPanel(wrapper);
expect(wrapper.find('.ant-calendar-my-select').first().text()).toBe('Jul2018');
wrapper.find('.ant-calendar-prev-year-btn').first().simulate('click');
wrapper.find('.ant-calendar-prev-month-btn').first().simulate('click');
expect(wrapper.find('.ant-calendar-my-select').first().text()).toBe('Jun2017');
expect(
wrapper
.find('.ant-calendar-my-select')
.first()
.text(),
).toBe('Jul2018');
wrapper
.find('.ant-calendar-prev-year-btn')
.first()
.simulate('click');
wrapper
.find('.ant-calendar-prev-month-btn')
.first()
.simulate('click');
expect(
wrapper
.find('.ant-calendar-my-select')
.first()
.text(),
).toBe('Jun2017');
});
// https://github.com/ant-design/ant-design/issues/11631
it('triggers onOpenChange when click on preset range', () => {
const handleOpenChange = jest.fn();
const range = [moment().subtract(2, 'd'), moment()];
const wrapper = mount(<RangePicker onOpenChange={handleOpenChange} ranges={{ 'recent two days': range }} />);
const wrapper = mount(
<RangePicker onOpenChange={handleOpenChange} ranges={{ 'recent two days': range }} />,
);
wrapper.find('.ant-calendar-picker-input').simulate('click');
wrapper.find('.ant-calendar-range-quick-selector Tag').simulate('click');
expect(handleOpenChange).toBeCalledWith(false);

4
components/date-picker/__tests__/WeekPicker.test.js

@ -9,9 +9,7 @@ describe('WeekPicker', () => {
focusTest(WeekPicker);
it('should support style prop', () => {
const wrapper = mount(
<WeekPicker style={{ width: 400 }} />
);
const wrapper = mount(<WeekPicker style={{ width: 400 }} />);
expect(wrapper.render()).toMatchSnapshot();
});
});

31
components/date-picker/__tests__/focus.test.js

@ -3,10 +3,7 @@ import { mount } from 'enzyme';
import moment from 'moment';
import MockDate from 'mockdate';
import DatePicker from '..';
import {
selectDate,
openPanel,
} from './utils';
import { selectDate, openPanel } from './utils';
const { MonthPicker, WeekPicker, RangePicker } = DatePicker;
@ -20,18 +17,14 @@ describe('DatePicker', () => {
});
it('should focus trigger input after select date in DatePicker', () => {
const wrapper = mount(
<DatePicker />
);
const wrapper = mount(<DatePicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'));
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).toBe(document.activeElement);
});
it('should focus trigger input after select date in RangePicker', () => {
const wrapper = mount(
<RangePicker />
);
const wrapper = mount(<RangePicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'), 0);
selectDate(wrapper, moment('2016-11-28'), 1);
@ -39,19 +32,21 @@ describe('DatePicker', () => {
});
it('should focus trigger input after select date in MonthPicker', () => {
const wrapper = mount(
<MonthPicker />
);
const wrapper = mount(<MonthPicker />);
openPanel(wrapper);
wrapper.find('.ant-calendar-month-panel-month').first().simulate('click');
wrapper.find('.ant-calendar-month-panel-cell').at(6).hasClass('ant-calendar-month-panel-selected-cell');
wrapper
.find('.ant-calendar-month-panel-month')
.first()
.simulate('click');
wrapper
.find('.ant-calendar-month-panel-cell')
.at(6)
.hasClass('ant-calendar-month-panel-selected-cell');
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).toBe(document.activeElement);
});
it('should focus trigger input after select date in WeekPicker', () => {
const wrapper = mount(
<WeekPicker />
);
const wrapper = mount(<WeekPicker />);
openPanel(wrapper);
selectDate(wrapper, moment('2016-11-23'));
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode()).toBe(document.activeElement);

26
components/date-picker/__tests__/other.test.js

@ -8,19 +8,29 @@ const { MonthPicker, WeekPicker } = DatePicker;
describe('MonthPicker and WeekPicker', () => {
it('render MonthPicker', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD').locale('zh-cn');
const wrapper = mount(
<MonthPicker open />
);
const wrapper = mount(<MonthPicker open />);
wrapper.setProps({ value: birthday });
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
it('render WeekPicker', () => {
const birthday = moment('2000-01-01', 'YYYY-MM-DD').locale('zh-cn');
const wrapper = mount(
<WeekPicker open />
);
const wrapper = mount(<WeekPicker open />);
wrapper.setProps({ value: birthday });
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
expect(
render(
wrapper
.find('Trigger')
.instance()
.getComponent(),
),
).toMatchSnapshot();
});
});

110
components/date-picker/__tests__/showTime.test.js

@ -9,11 +9,19 @@ describe('DatePicker with showTime', () => {
const onChangeFn = jest.fn();
const onOpenChangeFn = jest.fn();
const wrapper = mount(
<DatePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />
<DatePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />,
);
const calendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
calendarWrapper.find('.ant-calendar-date').at(0).simulate('click');
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper
.find('.ant-calendar-date')
.at(0)
.simulate('click');
expect(onChangeFn).toHaveBeenCalled();
expect(onOpenChangeFn).not.toHaveBeenCalled();
});
@ -24,10 +32,21 @@ describe('DatePicker with showTime', () => {
const onChangeFn = jest.fn();
const wrapper = mount(
<DatePicker showTime open onChange={onChangeFn} onOk={onOkFn} onOpenChange={onOpenChangeFn} />
<DatePicker
showTime
open
onChange={onChangeFn}
onOk={onOkFn}
onOpenChange={onOpenChangeFn}
/>,
);
const calendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper.find('.ant-calendar-ok-btn').simulate('click');
expect(onOkFn).toHaveBeenCalled();
expect(onOpenChangeFn).toHaveBeenCalledWith(false);
@ -39,22 +58,33 @@ describe('DatePicker with showTime', () => {
const onChangeFn = jest.fn();
const wrapper = mount(
<DatePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />
<DatePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />,
);
const calendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper.find('.ant-calendar-today-btn').simulate('click');
expect(onOpenChangeFn).toHaveBeenCalledWith(false);
expect(onChangeFn).toHaveBeenCalled();
});
it('should have correct className when use12Hours is true', () => {
const wrapper = mount(
<DatePicker showTime={{ use12Hours: true }} open />
const wrapper = mount(<DatePicker showTime={{ use12Hours: true }} open />);
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
const calendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(calendarWrapper.find('.ant-calendar-time-picker-column-4').length).toBe(0);
calendarWrapper.find('.ant-calendar-time-picker-btn').at(0).simulate('click');
calendarWrapper
.find('.ant-calendar-time-picker-btn')
.at(0)
.simulate('click');
expect(calendarWrapper.find('.ant-calendar-time-picker-column-4').hostNodes().length).toBe(1);
});
});
@ -64,16 +94,39 @@ describe('RangePicker with showTime', () => {
const onChangeFn = jest.fn();
const onOpenChangeFn = jest.fn();
const wrapper = mount(
<RangePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />
<RangePicker showTime open onChange={onChangeFn} onOpenChange={onOpenChangeFn} />,
);
const calendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
expect(calendarWrapper.find('.ant-calendar-time-picker-btn').hasClass('ant-calendar-time-picker-btn-disabled')).toBe(true);
expect(calendarWrapper.find('.ant-calendar-ok-btn').hasClass('ant-calendar-ok-btn-disabled')).toBe(true);
calendarWrapper.find('.ant-calendar-date').at(10).simulate('click');
calendarWrapper.find('.ant-calendar-date').at(11).simulate('click');
expect(calendarWrapper.find('.ant-calendar-time-picker-btn').hasClass('ant-calendar-time-picker-btn-disabled')).toBe(false);
expect(calendarWrapper.find('.ant-calendar-ok-btn').hasClass('ant-calendar-ok-btn-disabled')).toBe(false);
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
expect(
calendarWrapper
.find('.ant-calendar-time-picker-btn')
.hasClass('ant-calendar-time-picker-btn-disabled'),
).toBe(true);
expect(
calendarWrapper.find('.ant-calendar-ok-btn').hasClass('ant-calendar-ok-btn-disabled'),
).toBe(true);
calendarWrapper
.find('.ant-calendar-date')
.at(10)
.simulate('click');
calendarWrapper
.find('.ant-calendar-date')
.at(11)
.simulate('click');
expect(
calendarWrapper
.find('.ant-calendar-time-picker-btn')
.hasClass('ant-calendar-time-picker-btn-disabled'),
).toBe(false);
expect(
calendarWrapper.find('.ant-calendar-ok-btn').hasClass('ant-calendar-ok-btn-disabled'),
).toBe(false);
expect(onChangeFn).toHaveBeenCalled();
expect(onOpenChangeFn).not.toHaveBeenCalled();
});
@ -89,12 +142,23 @@ describe('RangePicker with showTime', () => {
onOk={onOkFn}
onChange={onChangeFn}
onOpenChange={onOpenChangeFn}
/>
/>,
);
const calendarWrapper = mount(wrapper.find('Trigger').instance().getComponent());
calendarWrapper.find('.ant-calendar-date').at(10).simulate('click');
calendarWrapper.find('.ant-calendar-date').at(11).simulate('click');
const calendarWrapper = mount(
wrapper
.find('Trigger')
.instance()
.getComponent(),
);
calendarWrapper
.find('.ant-calendar-date')
.at(10)
.simulate('click');
calendarWrapper
.find('.ant-calendar-date')
.at(11)
.simulate('click');
onChangeFn.mockClear();
calendarWrapper.find('.ant-calendar-ok-btn').simulate('click');
expect(onOkFn).toHaveBeenCalled();

9
components/date-picker/__tests__/utils.js

@ -8,7 +8,9 @@ export function selectDate(wrapper, date, index) {
}
export function hasSelected(wrapper, date) {
return wrapper.find({ title: date.format('LL'), role: 'gridcell' }).hasClass('ant-calendar-selected-day');
return wrapper
.find({ title: date.format('LL'), role: 'gridcell' })
.hasClass('ant-calendar-selected-day');
}
export function openPanel(wrapper) {
@ -16,7 +18,10 @@ export function openPanel(wrapper) {
}
export function clearInput(wrapper) {
wrapper.find('.ant-calendar-picker-clear').hostNodes().simulate('click');
wrapper
.find('.ant-calendar-picker-clear')
.hostNodes()
.simulate('click');
}
export function nextYear(wrapper) {

65
components/date-picker/createPicker.tsx

@ -58,7 +58,7 @@ export default function createPicker(TheCalendar: React.ComponentClass): any {
if (value && !interopDefault(moment).isMoment(value)) {
throw new Error(
'The value/defaultValue of DatePicker or MonthPicker must be ' +
'a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value',
'a moment object after `antd@2.0`, see: https://u.ant.design/date-picker-value',
);
}
this.state = {
@ -71,17 +71,15 @@ export default function createPicker(TheCalendar: React.ComponentClass): any {
renderFooter = (...args: any[]) => {
const { prefixCls, renderExtraFooter } = this.props;
return renderExtraFooter ? (
<div className={`${prefixCls}-footer-extra`}>
{renderExtraFooter(...args)}
</div>
<div className={`${prefixCls}-footer-extra`}>{renderExtraFooter(...args)}</div>
) : null;
}
};
clearSelection = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
e.stopPropagation();
this.handleChange(null);
}
};
handleChange = (value: moment.Moment | null) => {
const props = this.props;
@ -92,11 +90,11 @@ export default function createPicker(TheCalendar: React.ComponentClass): any {
});
}
props.onChange(value, (value && value.format(props.format)) || '');
}
};
handleCalendarChange = (value: moment.Moment) => {
this.setState({ showDate: value });
}
};
handleOpenChange = (open: boolean) => {
const { onOpenChange } = this.props;
@ -123,15 +121,14 @@ export default function createPicker(TheCalendar: React.ComponentClass): any {
saveInput = (node: any) => {
this.input = node;
}
};
render() {
const { value, showDate, open } = this.state;
const props = omit(this.props, ['onChange']);
const { prefixCls, locale, localeCode, suffixIcon } = props;
const placeholder = ('placeholder' in props)
? props.placeholder : locale.lang.placeholder;
const placeholder = 'placeholder' in props ? props.placeholder : locale.lang.placeholder;
const disabledTime = props.showTime ? props.disabledTime : null;
@ -162,7 +159,10 @@ export default function createPicker(TheCalendar: React.ComponentClass): any {
calendarProps.mode = props.mode;
}
warning(!('onOK' in props), 'It should be `DatePicker[onOk]` or `MonthPicker[onOk]`, instead of `onOK`!');
warning(
!('onOK' in props),
'It should be `DatePicker[onOk]` or `MonthPicker[onOk]`, instead of `onOK`!',
);
const calendar = (
<TheCalendar
{...calendarProps}
@ -186,28 +186,27 @@ export default function createPicker(TheCalendar: React.ComponentClass): any {
/>
);
const clearIcon = (!props.disabled && props.allowClear && value) ? (
<Icon
type="close-circle"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
theme="filled"
/>
) : null;
const clearIcon =
!props.disabled && props.allowClear && value ? (
<Icon
type="close-circle"
className={`${prefixCls}-picker-clear`}
onClick={this.clearSelection}
theme="filled"
/>
) : null;
const inputIcon = suffixIcon && (
React.isValidElement<{ className?: string }>(suffixIcon)
? React.cloneElement(
suffixIcon,
{
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
},
) : <span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>) || (
<Icon type="calendar" className={`${prefixCls}-picker-icon`} />
);
const inputIcon = (suffixIcon &&
(React.isValidElement<{ className?: string }>(suffixIcon) ? (
React.cloneElement(suffixIcon, {
className: classNames({
[suffixIcon.props.className!]: suffixIcon.props.className,
[`${prefixCls}-picker-icon`]: true,
}),
})
) : (
<span className={`${prefixCls}-picker-icon`}>{suffixIcon}</span>
))) || <Icon type="calendar" className={`${prefixCls}-picker-icon`} />;
const dataOrAriaProps = getDataOrAriaProps(props);
const input = ({ value: inputValue }: { value: moment.Moment | null }) => (

4
components/date-picker/index.tsx

@ -7,7 +7,9 @@ import RangePicker from './RangePicker';
import WeekPicker from './WeekPicker';
import { DatePickerProps, DatePickerDecorator } from './interface';
const DatePicker = wrapPicker(createPicker(RcCalendar)) as React.ClassicComponentClass<DatePickerProps>;
const DatePicker = wrapPicker(createPicker(RcCalendar)) as React.ClassicComponentClass<
DatePickerProps
>;
const MonthPicker = wrapPicker(createPicker(MonthCalendar), 'YYYY-MM');

31
components/date-picker/interface.tsx

@ -36,10 +36,12 @@ export interface DatePickerProps extends PickerProps, SinglePickerProps {
showTime?: TimePickerProps | boolean;
showToday?: boolean;
open?: boolean;
disabledTime?: (current: moment.Moment) => {
disabledHours?: () => number[],
disabledMinutes?: () => number[],
disabledSeconds?: () => number[],
disabledTime?: (
current: moment.Moment,
) => {
disabledHours?: () => number[];
disabledMinutes?: () => number[];
disabledSeconds?: () => number[];
};
onOpenChange?: (status: boolean) => void;
onOk?: (selectedTime: RangePickerValue) => void;
@ -52,10 +54,10 @@ export interface MonthPickerProps extends PickerProps, SinglePickerProps {
}
export type RangePickerValue =
undefined[] |
[moment.Moment] |
[undefined, moment.Moment] |
[moment.Moment, moment.Moment];
| undefined[]
| [moment.Moment]
| [undefined, moment.Moment]
| [moment.Moment, moment.Moment];
export type RangePickerPresetRange = RangePickerValue | (() => RangePickerValue);
export interface RangePickerProps extends PickerProps {
@ -68,14 +70,17 @@ export interface RangePickerProps extends PickerProps {
onOk?: (selectedTime: moment.Moment) => void;
showTime?: TimePickerProps | boolean;
ranges?: {
[range: string]: RangePickerPresetRange,
[range: string]: RangePickerPresetRange;
};
placeholder?: [string, string];
mode?: string | string[];
disabledTime?: (current: moment.Moment, type: string) => {
disabledHours?: () => number[],
disabledMinutes?: () => number[],
disabledSeconds?: () => number[],
disabledTime?: (
current: moment.Moment,
type: string,
) => {
disabledHours?: () => number[];
disabledMinutes?: () => number[];
disabledSeconds?: () => number[];
};
onPanelChange?: (value?: RangePickerValue, mode?: string | string[]) => void;
}

5
components/date-picker/locale/example.json

@ -1,10 +1,7 @@
{
"lang": {
"placeholder": "Select date",
"rangePlaceholder": [
"Start date",
"End date"
],
"rangePlaceholder": ["Start date", "End date"],
"today": "Today",
"now": "Now",
"backToToday": "Back to today",

2
components/date-picker/locale/it_IT.tsx

@ -5,7 +5,7 @@ import TimePickerLocale from '../../time-picker/locale/it_IT';
const locale = {
lang: {
placeholder: 'Selezionare la data',
rangePlaceholder: ['Data d\'inizio', 'Data di fine'],
rangePlaceholder: ["Data d'inizio", 'Data di fine'],
...CalendarLocale,
},
timePickerLocale: {

8
components/date-picker/style/Calendar.less

@ -38,7 +38,7 @@
position: absolute;
top: 0;
color: @text-color-secondary;
font-family: Arial, "Hiragino Sans GB", "Microsoft Yahei", "Microsoft Sans Serif", sans-serif;
font-family: Arial, 'Hiragino Sans GB', 'Microsoft Yahei', 'Microsoft Sans Serif', sans-serif;
padding: 0 5px;
font-size: 16px;
display: inline-block;
@ -214,7 +214,9 @@
background: tint(@primary-color, 80%);
}
&-selected-date, &-selected-start-date, &-selected-end-date {
&-selected-date,
&-selected-start-date,
&-selected-end-date {
.@{calendar-prefix-cls}-date {
background: @primary-color;
color: #fff;
@ -243,7 +245,7 @@
padding-right: 5px;
padding-left: 5px;
&:before {
content: " ";
content: ' ';
position: absolute;
top: -1px;
left: 5px;

4
components/date-picker/style/DecadePanel.less

@ -15,11 +15,11 @@
}
.@{calendar-prefix-cls}-decade-panel-header {
.calendarPanelHeader(~"@{calendar-prefix-cls}-decade-panel");
.calendarPanelHeader(~'@{calendar-prefix-cls}-decade-panel');
}
.@{calendar-prefix-cls}-decade-panel-body {
height: ~"calc(100% - 40px)";
height: ~'calc(100% - 40px)';
}
.@{calendar-prefix-cls}-decade-panel-table {

7
components/date-picker/style/MonthPanel.less

@ -9,7 +9,8 @@
background: @component-background;
outline: none;
> div { // TODO: this is a useless wrapper, and we need to remove it in rc-calendar
> div {
// TODO: this is a useless wrapper, and we need to remove it in rc-calendar
height: 100%;
}
}
@ -19,11 +20,11 @@
}
.@{calendar-prefix-cls}-month-panel-header {
.calendarPanelHeader(~"@{calendar-prefix-cls}-month-panel");
.calendarPanelHeader(~'@{calendar-prefix-cls}-month-panel');
}
.@{calendar-prefix-cls}-month-panel-body {
height: ~"calc(100% - 40px)";
height: ~'calc(100% - 40px)';
}
.@{calendar-prefix-cls}-month-panel-table {

6
components/date-picker/style/Picker.less

@ -1,4 +1,4 @@
@import "../../button/style/mixin";
@import '../../button/style/mixin';
.@{calendar-prefix-cls}-picker-container {
.reset-component;
@ -65,7 +65,7 @@
margin-top: -7px;
line-height: 14px;
font-size: @font-size-sm;
transition: all .3s;
transition: all 0.3s;
user-select: none;
z-index: 1;
}
@ -89,7 +89,7 @@
}
&-icon {
font-family: "anticon";
font-family: 'anticon';
font-size: @font-size-base;
color: @disabled-color;
display: inline-block;

4
components/date-picker/style/RangePicker.less

@ -28,7 +28,7 @@
.@{calendar-prefix-cls}-date-panel {
&::after {
content: ".";
content: '.';
display: block;
height: 0;
clear: both;
@ -134,7 +134,7 @@
z-index: 1;
}
&:before {
content: "";
content: '';
display: block;
background: @item-active-bg;
border-radius: 0;

2
components/date-picker/style/TimePicker.less

@ -90,7 +90,7 @@
}
li:last-child:after {
content: "";
content: '';
height: 202px;
display: block;
}

2
components/date-picker/style/WeekPicker.less

@ -3,7 +3,7 @@
opacity: 0.5;
}
.@{calendar-prefix-cls}-body tr {
transition: all .3s;
transition: all 0.3s;
cursor: pointer;
&:hover {
background: @primary-1;

7
components/date-picker/style/YearPanel.less

@ -9,7 +9,8 @@
background: @component-background;
outline: none;
> div { // TODO: this is a useless wrapper, and we need to remove it in rc-calendar
> div {
// TODO: this is a useless wrapper, and we need to remove it in rc-calendar
height: 100%;
}
}
@ -19,11 +20,11 @@
}
.@{calendar-prefix-cls}-year-panel-header {
.calendarPanelHeader(~"@{calendar-prefix-cls}-year-panel");
.calendarPanelHeader(~'@{calendar-prefix-cls}-year-panel');
}
.@{calendar-prefix-cls}-year-panel-body {
height: ~"calc(100% - 40px)";
height: ~'calc(100% - 40px)';
}
.@{calendar-prefix-cls}-year-panel-table {

30
components/date-picker/style/index.less

@ -1,17 +1,17 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import "../../input/style/mixin";
@import "../../button/style/mixin";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@import '../../button/style/mixin';
@calendar-prefix-cls: ~"@{ant-prefix}-calendar";
@calendar-timepicker-prefix-cls: ~"@{ant-prefix}-calendar-time-picker";
@calendar-prefix-cls: ~'@{ant-prefix}-calendar';
@calendar-timepicker-prefix-cls: ~'@{ant-prefix}-calendar-time-picker';
@import "Picker";
@import "Calendar";
@import "RangePicker";
@import "TimePicker";
@import "MonthPanel";
@import "YearPanel";
@import "DecadePanel";
@import "MonthPicker";
@import "WeekPicker";
@import 'Picker';
@import 'Calendar';
@import 'RangePicker';
@import 'TimePicker';
@import 'MonthPanel';
@import 'YearPanel';
@import 'DecadePanel';
@import 'MonthPicker';
@import 'WeekPicker';

40
components/date-picker/wrapPicker.tsx

@ -28,12 +28,9 @@ export default function wrapPicker(Picker: React.ComponentClass<any>, defaultFor
format: defaultFormat || 'YYYY-MM-DD',
transitionName: 'slide-up',
popupStyle: {},
onChange() {
},
onOk() {
},
onOpenChange() {
},
onChange() {},
onOk() {},
onOpenChange() {},
locale: {},
prefixCls: 'ant-calendar',
inputPrefixCls: 'ant-input',
@ -51,35 +48,35 @@ export default function wrapPicker(Picker: React.ComponentClass<any>, defaultFor
handleOpenChange = (open: boolean) => {
const { onOpenChange } = this.props;
onOpenChange(open);
}
};
handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
handleFocus: React.FocusEventHandler<HTMLInputElement> = e => {
const { onFocus } = this.props;
if (onFocus) {
onFocus(e);
}
}
};
handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
handleBlur: React.FocusEventHandler<HTMLInputElement> = e => {
const { onBlur } = this.props;
if (onBlur) {
onBlur(e);
}
}
};
handleMouseEnter: React.MouseEventHandler<HTMLInputElement> = (e) => {
handleMouseEnter: React.MouseEventHandler<HTMLInputElement> = e => {
const { onMouseEnter } = this.props;
if (onMouseEnter) {
onMouseEnter(e);
}
}
};
handleMouseLeave: React.MouseEventHandler<HTMLInputElement> = (e) => {
handleMouseLeave: React.MouseEventHandler<HTMLInputElement> = e => {
const { onMouseLeave } = this.props;
if (onMouseLeave) {
onMouseLeave(e);
}
}
};
focus() {
this.picker.focus();
@ -91,7 +88,7 @@ export default function wrapPicker(Picker: React.ComponentClass<any>, defaultFor
savePicker = (node: any) => {
this.picker = node;
}
};
getDefaultLocale = () => {
const result = {
@ -103,7 +100,7 @@ export default function wrapPicker(Picker: React.ComponentClass<any>, defaultFor
...(this.props.locale || {}).lang,
};
return result;
}
};
renderPicker = (locale: any, localeCode: string) => {
const props = this.props;
@ -121,7 +118,7 @@ export default function wrapPicker(Picker: React.ComponentClass<any>, defaultFor
const rcTimePickerProps = {
...generateShowHourMinuteSecond(timeFormat),
format: timeFormat,
use12Hours: (props.showTime && props.showTime.use12Hours),
use12Hours: props.showTime && props.showTime.use12Hours,
};
const columns = getColumns(rcTimePickerProps);
const timePickerCls = `${prefixCls}-time-picker-column-${columns}`;
@ -152,14 +149,11 @@ export default function wrapPicker(Picker: React.ComponentClass<any>, defaultFor
onMouseLeave={this.handleMouseLeave}
/>
);
}
};
render() {
return (
<LocaleReceiver
componentName="DatePicker"
defaultLocale={this.getDefaultLocale}
>
<LocaleReceiver componentName="DatePicker" defaultLocale={this.getDefaultLocale}>
{this.renderPicker}
</LocaleReceiver>
);

14
components/divider/index.tsx

@ -20,12 +20,16 @@ export default function Divider({
dashed,
...restProps
}: DividerProps) {
const orientationPrefix = (orientation.length > 0) ? '-' + orientation : orientation;
const orientationPrefix = orientation.length > 0 ? '-' + orientation : orientation;
const classString = classNames(
className, `${prefixCls}-divider`, `${prefixCls}-divider-${type}`, {
[`${prefixCls}-divider-with-text${orientationPrefix}`]: children,
[`${prefixCls}-divider-dashed`]: !!dashed,
});
className,
`${prefixCls}-divider`,
`${prefixCls}-divider-${type}`,
{
[`${prefixCls}-divider-with-text${orientationPrefix}`]: children,
[`${prefixCls}-divider-dashed`]: !!dashed,
},
);
return (
<div className={classString} {...restProps}>
{children && <span className={`${prefixCls}-divider-inner-text`}>{children}</span>}

8
components/divider/style/index.less

@ -1,7 +1,7 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@import '../../style/themes/default';
@import '../../style/mixins/index';
@divider-prefix-cls: ~"@{ant-prefix}-divider";
@divider-prefix-cls: ~'@{ant-prefix}-divider';
.@{divider-prefix-cls} {
.reset-component;
@ -39,7 +39,7 @@
margin: 16px 0;
&:before,
&:after {
content: "";
content: '';
display: table-cell;
position: relative;
top: 50%;

50
components/drawer/__tests__/Drawer.test.js

@ -5,80 +5,54 @@ import Drawer from '..';
describe('Drawer', () => {
it('render correctly', () => {
const wrapper = render(
<Drawer
visible
width={400}
getContainer={false}
>
<Drawer visible width={400} getContainer={false}>
Here is content of Drawer
</Drawer>
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});
it('render top drawer', () => {
const wrapper = render(
<Drawer
visible
height={400}
placement="top"
getContainer={false}
>
<Drawer visible height={400} placement="top" getContainer={false}>
Here is content of Drawer
</Drawer>
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});
it('have a title', () => {
const wrapper = render(
<Drawer
visible
title="Test Title"
getContainer={false}
>
<Drawer visible title="Test Title" getContainer={false}>
Here is content of Drawer
</Drawer>
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});
it('closable is false', () => {
const wrapper = render(
<Drawer
visible
closable={false}
getContainer={false}
>
<Drawer visible closable={false} getContainer={false}>
Here is content of Drawer
</Drawer>
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});
it('destroyOnClose is true', () => {
const wrapper = render(
<Drawer
destroyOnClose
visible={false}
getContainer={false}
>
<Drawer destroyOnClose visible={false} getContainer={false}>
Here is content of Drawer
</Drawer>
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});
it('className is test_drawer', () => {
const wrapper = render(
<Drawer
destroyOnClose
visible={false}
className="test_drawer"
getContainer={false}
>
<Drawer destroyOnClose visible={false} className="test_drawer" getContainer={false}>
Here is content of Drawer
</Drawer>
</Drawer>,
);
expect(wrapper).toMatchSnapshot();
});

10
components/drawer/__tests__/DrawerEvent.test.js

@ -23,19 +23,14 @@ class DrawerEventTester extends React.Component {
this.setState({
visible: true,
});
}
};
render() {
const { visible } = this.state;
return (
<div>
<Button onClick={this.open}>open</Button>
<Drawer
visible={visible}
onClose={this.onClose}
getContainer={false}
{...this.props}
>
<Drawer visible={visible} onClose={this.onClose} getContainer={false} {...this.props}>
Here is content of Drawer
</Drawer>
</div>
@ -43,7 +38,6 @@ class DrawerEventTester extends React.Component {
}
}
describe('Drawer', () => {
it('render correctly', () => {
const wrapper = mount(<DrawerEventTester />);

6
components/drawer/__tests__/MultiDrawer.test.js

@ -48,7 +48,7 @@ class MultiDrawer extends React.Component {
visible={visible}
>
<Button type="primary" id="open_two_drawer" onClick={this.showChildrenDrawer}>
Two-level drawer
Two-level drawer
</Button>
<Drawer
title="Two-level Drawer"
@ -59,9 +59,7 @@ class MultiDrawer extends React.Component {
onClose={this.onChildrenDrawerClose}
visible={childrenDrawer}
>
<div id="two_drawer_text">
This is two-level drawer
</div>
<div id="two_drawer_text">This is two-level drawer</div>
</Drawer>
<div
style={{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save