Merge branch 'antd-3.0'

This commit is contained in:
Wei Zhu 2017-12-01 13:19:38 +08:00
commit 7fa0599695
592 changed files with 69434 additions and 9016 deletions

View File

@ -42,4 +42,9 @@ module.exports = {
snapshotSerializers: [
'enzyme-to-json/serializer',
],
globals: {
'ts-jest': {
tsConfigFile: './tsconfig.test.json',
}
},
};

View File

@ -18,4 +18,9 @@ module.exports = {
snapshotSerializers: [
'enzyme-to-json/serializer'
],
globals: {
'ts-jest': {
tsConfigFile: './tsconfig.test.json',
}
},
};

View File

@ -16,6 +16,8 @@
"selector-list-comma-newline-after": null,
"selector-pseudo-element-colon-notation": null,
"unit-no-unknown": null,
"value-list-max-empty-lines": null
"value-list-max-empty-lines": null,
"font-family-no-missing-generic-family-keyword": null,
"no-descending-specificity": null
}
}

View File

@ -5,33 +5,24 @@ language: node_js
node_js:
- 8
env:
matrix:
- TEST_TYPE=lint
- TEST_TYPE=test:dist
- TEST_TYPE=test:lib
- TEST_TYPE=test:es
- TEST_TYPE=test:dom
- TEST_TYPE=test:node
matrix:
fast_finish: true
include:
- env: TEST_TYPE=lint
- env: REACT=16 TEST_TYPE=test:dist
- env: REACT=16 TEST_TYPE=test:lib
- env: REACT=16 TEST_TYPE=test:es
- env: REACT=16 TEST_TYPE=test:dom
- env: REACT=16 TEST_TYPE=test:node
- env: REACT=15 TEST_TYPE=test:dist
- env: REACT=15 TEST_TYPE=test:lib
- env: REACT=15 TEST_TYPE=test:es
- env: REACT=15 TEST_TYPE=test:dom
- env: REACT=15 TEST_TYPE=test:node
- env: REACT=16 TEST_TYPE=bisheng:build
before_script:
- scripts/install-react.sh
script:
- |
if [ "$TEST_TYPE" = lint ]; then
npm run lint
elif [ "$TEST_TYPE" = test:dist ]; then
npm run dist && \
node ./tests/dekko/dist.test.js && \
LIB_DIR=dist npm test -- -w 2
elif [ "$TEST_TYPE" = test:lib ]; then
npm run compile && \
node ./tests/dekko/lib.test.js && \
LIB_DIR=lib npm test -- -w 2
elif [ "$TEST_TYPE" = test:es ]; then
npm run compile && \
LIB_DIR=es npm test -- -w 2
elif [ "$TEST_TYPE" = test:dom ]; then
npm test -- --coverage -w 2 && \
bash <(curl -s https://codecov.io/bash)
elif [ "$TEST_TYPE" = test:node ]; then
npm run test-node -- -w 2
fi
- scripts/travis-script.sh

View File

@ -15,7 +15,58 @@ timeline: true
如果需要查看 `2.0.0` 之前的更新日志,请移步 [GitHub](https://github.com/ant-design/ant-design/blob/1.x-stable/CHANGELOG.md)。
---
---## 3.0.0
- Select
- 单选和多选模式 Option 支持 number。
- 新增 `maxTagCount``maxTagPlaceholder`
- 新增 `showAction`
- 新增 `onMouseEnter``onMouseLeave`
- 新增 `focus()`、`blur()` 和 `autoFocus`
- Table
- 新增 `components` 属性,可以通过该属性覆盖 table 默认元素:
```javascript
const components = {
table: MyTable,
header: {
wrapper: HeaderWrapper,
row: HeaderRow,
cell: HeaderCell,
},
body: {
wrapper: BodyWrapper,
row: BodyRow,
cell: BodyCell,
},
};
<Table components={components} columns={columns data={data}} />
```
- 新增 `onRow` 用于设置行属性。
- 新增 `onHeaderRow` 用于设置头部的行属性。
- 新增 `column[onCell]` 用于设置单元格属性 。
- 新增 `column[onHeaderCell]` 用于设置头部单元格属性。
- 新增 `column[align]` 用于设置列文字对其。
- 废弃以下属性
- `onRowClick`
- `onRowDoubleClick`
- `onRowContextMenu`
- 'onRowMouseEnter'
- 'onRowMouseLeave'
以上属性请使用 `onRow` 代替:
```javascript
<Table onRow={(record) => ({
onClick: () => {},
onDoubleClick: () => {},
onContextMenu: () => {},
onMouseEnter: () => {},
onMouseLeave: () => {},
})} />
```
- 废弃 `getBodyWrapper`, 请使用 `components` 代替。
## 2.13.10

View File

@ -1,6 +1,6 @@
<p align="center">
<a href="http://ant.design">
<img width="320" src="https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg">
<img width="230" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
</a>
</p>
@ -60,24 +60,11 @@ import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
## TypeScript
```js
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"jsx": "preserve",
"allowSyntheticDefaultImports": true
}
}
```
> 注意:
> - 设置 `allowSyntheticDefaultImports` 避免 `error TS1192: Module 'react' has no default export` 的错误。
> - 不要使用 @types/antd, antd 已经自带了 TypeScript 定义。
参考 [在 TypeScript 中使用](https://ant.design/docs/react/use-in-typescript-cn)
## 国际化
参考 [国际化文档](http://ant.design/docs/react/i18n)。
参考 [国际化文档](http://ant.design/docs/react/i18n-cn)。
## 链接
@ -93,7 +80,7 @@ import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
- [开发者说明](https://github.com/ant-design/ant-design/wiki/Development)
- [版本发布规则](https://github.com/ant-design/ant-design/wiki/%E8%BD%AE%E5%80%BC%E8%A7%84%E5%88%99%E5%92%8C%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E6%B5%81%E7%A8%8B)
- [常见问题](https://github.com/ant-design/ant-design/wiki/FAQ)
- [CodePen 模板](http://codepen.io/benjycui/pen/KgPZrE?editors=001) for bug reports
- [CodeSandbox 模板](https://u.ant.design/codesandbox-repro) for bug reports
- [Awesome Ant Design](https://github.com/websemantics/awesome-ant-design)
- [定制主题](http://ant.design/docs/react/customize-theme-cn)

View File

@ -1,6 +1,6 @@
<p align="center">
<a href="http://ant.design">
<img width="320" src="https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg">
<img width="230" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
</a>
</p>
@ -86,33 +86,8 @@ import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
### TypeScript
```js
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"jsx": "preserve",
"allowSyntheticDefaultImports": true
}
}
```
See [Use in TypeScript](https://ant.design/docs/react/use-in-typescript)
> Note:
> - set `allowSyntheticDefaultImports` to prevent `error TS1192: Module 'react' has no default export`.
> - Don't use @types/antd, antd provide a built-in ts definition already.
#### Use [ts-import-plugin](https://github.com/Brooooooklyn/ts-import-plugin) with modularized antd
```js
{
loader: "ts-loader", // or awesome-typescript-loader
options {
getCustomTransformers: () => ({
before: [ tsImportPluginFactory({ libraryName: "antd", style: "css" }) ]
})
}
}
```
## Internationalization
@ -131,7 +106,7 @@ See [i18n](http://ant.design/docs/react/i18n).
- [Developer Instruction](https://github.com/ant-design/ant-design/wiki/Development)
- [Versioning Release Note](https://github.com/ant-design/ant-design/wiki/%E8%BD%AE%E5%80%BC%E8%A7%84%E5%88%99%E5%92%8C%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E6%B5%81%E7%A8%8B)
- [FAQ](https://github.com/ant-design/ant-design/wiki/FAQ)
- [CodePen boilerplate](http://codepen.io/benjycui/pen/KgPZrE?editors=001) for bug reports
- [CodeSandbox Template](https://u.ant.design/codesandbox-repro) for bug reports
- [Awesome Ant Design](https://github.com/websemantics/awesome-ant-design)
- [Customize Theme](http://ant.design/docs/react/customize-theme)

View File

@ -19,12 +19,14 @@ Array [
"Checkbox",
"Col",
"DatePicker",
"Divider",
"Dropdown",
"Form",
"Icon",
"Input",
"InputNumber",
"Layout",
"List",
"LocaleProvider",
"message",
"Menu",

View File

@ -0,0 +1,4 @@
// https://github.com/moment/moment/issues/3650
export default function callMoment(moment: any, ...args: any[]) {
return (moment.default || moment)(...args);
}

View File

@ -1,30 +0,0 @@
export function getComponentLocale(props, context, componentName, getDefaultLocale) {
let locale: any = {};
if (context && context.antLocale && context.antLocale[componentName]) {
locale = context.antLocale[componentName];
} else {
const defaultLocale = getDefaultLocale();
// TODO: make default lang of antd be English
// https://github.com/ant-design/ant-design/issues/6334
locale = defaultLocale.default || defaultLocale;
}
const result = {
...locale,
...props.locale,
};
result.lang = {
...locale.lang,
...props.locale.lang,
};
return result;
}
export function getLocaleCode(context) {
const localeCode = context.antLocale && context.antLocale.locale;
// Had use LocaleProvide but didn't set locale
if (context.antLocale && context.antLocale.exist && !localeCode) {
return 'zh-cn';
}
return localeCode;
}

View File

@ -2,7 +2,7 @@ const availablePrefixs = ['moz', 'ms', 'webkit'];
function requestAnimationFramePolyfill() {
let lastTime = 0;
return function(callback) {
return function(callback: (n: number) => void) {
const currTime = new Date().getTime();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
@ -23,11 +23,11 @@ export default function getRequestAnimationFrame() {
const prefix = availablePrefixs.filter(key => `${key}RequestAnimationFrame` in window)[0];
return prefix
? window[`${prefix}RequestAnimationFrame`]
? (window as any)[`${prefix}RequestAnimationFrame`]
: requestAnimationFramePolyfill();
}
export function cancelRequestAnimationFrame(id) {
export function cancelRequestAnimationFrame(id: number) {
if (typeof window === 'undefined') {
return null;
}
@ -39,6 +39,8 @@ export function cancelRequestAnimationFrame(id) {
)[0];
return prefix ?
(window[`${prefix}CancelAnimationFrame`] || window[`${prefix}CancelRequestAnimationFrame`]).call(this, id)
: clearTimeout(id);
(
(window as any)[`${prefix}CancelAnimationFrame`] ||
(window as any)[`${prefix}CancelRequestAnimationFrame`]
).call(this, id) : clearTimeout(id);
}

View File

@ -1,4 +1,4 @@
export default function getScroll(target, top): number {
export default function getScroll(target: any, top: boolean): number {
if (typeof window === 'undefined') {
return 0;
}

View File

@ -1,4 +1,4 @@
let animation;
let animation: boolean;
function isCssAnimationSupported() {
if (animation !== undefined) {
@ -11,7 +11,7 @@ function isCssAnimationSupported() {
}
if (animation !== undefined) {
for (let i = 0; i < domPrefixes.length; i++) {
if (elm.style[`${domPrefixes[i]}AnimationName`] !== undefined) {
if ((elm.style as any)[`${domPrefixes[i]}AnimationName`] !== undefined) {
animation = true;
break;
}

View File

@ -3,18 +3,18 @@ import getRequestAnimationFrame, { cancelRequestAnimationFrame } from './getRequ
const reqAnimFrame = getRequestAnimationFrame();
function animate(node, show, done) {
let height;
let requestAnimationFrameId;
function animate(node: HTMLElement, show: boolean, done: () => void) {
let height: number;
let requestAnimationFrameId: number;
return cssAnimation(node, 'ant-motion-collapse', {
start() {
if (!show) {
node.style.height = `${node.offsetHeight}px`;
node.style.opacity = 1;
node.style.opacity = '1';
} else {
height = node.offsetHeight;
node.style.height = 0;
node.style.opacity = 0;
node.style.height = '0 px';
node.style.opacity = '0';
}
},
active() {
@ -23,7 +23,7 @@ function animate(node, show, done) {
}
requestAnimationFrameId = reqAnimFrame(() => {
node.style.height = `${show ? height : 0}px`;
node.style.opacity = show ? 1 : 0;
node.style.opacity = show ? '1' : '0';
});
},
end() {
@ -38,13 +38,13 @@ function animate(node, show, done) {
}
const animation = {
enter(node, done) {
enter(node: HTMLElement, done: () => void) {
return animate(node, true, done);
},
leave(node, done) {
leave(node: HTMLElement, done: () => void) {
return animate(node, false, done);
},
appear(node, done) {
appear(node: HTMLElement, done: () => void) {
return animate(node, true, done);
},
};

View File

@ -2,27 +2,27 @@ import getRequestAnimationFrame, { cancelRequestAnimationFrame } from '../_util/
const reqAnimFrame = getRequestAnimationFrame();
export default function throttleByAnimationFrame(fn) {
let requestId;
export default function throttleByAnimationFrame(fn: () => void) {
let requestId: number | null;
const later = args => () => {
const later = (args: any[]) => () => {
requestId = null;
fn(...args);
};
const throttled = (...args) => {
const throttled = (...args: any[]) => {
if (requestId == null) {
requestId = reqAnimFrame(later(args));
}
};
(throttled as any).cancel = () => cancelRequestAnimationFrame(requestId);
(throttled as any).cancel = () => cancelRequestAnimationFrame(requestId!);
return throttled;
}
export function throttleByAnimationFrameDecorator() {
return function(target, key, descriptor) {
return function(target: any, key: string, descriptor: any) {
let fn = descriptor.value;
let definingProperty = false;
return {

View File

@ -1,4 +1,4 @@
export default function triggerEvent(el, type) {
export default function triggerEvent(el: Element, type: string) {
if ('createEvent' in document) {
// modern browsers, IE9+
const e = document.createEvent('HTMLEvents');

View File

@ -60,7 +60,7 @@ describe('Affix Render', () => {
const wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
jest.runAllTimers();
wrapper.node.affix.refs.fixedNode.parentNode.getBoundingClientRect = jest.fn(() => {
wrapper.instance().affix.fixedNode.parentNode.getBoundingClientRect = jest.fn(() => {
return {
bottom: 100, height: 28, left: 0, right: 0, top: -50, width: 195,
};
@ -71,6 +71,6 @@ describe('Affix Render', () => {
});
jest.runAllTimers();
expect(wrapper.node.affix.state.affixStyle).not.toBe(null);
expect(wrapper.instance().affix.state.affixStyle).not.toBe(null);
});
});

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import classNames from 'classnames';
@ -8,13 +8,13 @@ import omit from 'omit.js';
import getScroll from '../_util/getScroll';
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
function getTargetRect(target): ClientRect {
function getTargetRect(target: HTMLElement | Window | null): ClientRect {
return target !== window ?
target.getBoundingClientRect() :
{ top: 0, left: 0, bottom: 0 };
(target as HTMLElement).getBoundingClientRect() :
{ top: 0, left: 0, bottom: 0 } as ClientRect;
}
function getOffset(element: HTMLElement, target) {
function getOffset(element: HTMLElement, target: HTMLElement | Window | null) {
const elemRect = element.getBoundingClientRect();
const targetRect = getTargetRect(target);
@ -55,11 +55,16 @@ export interface AffixProps {
/** 固定状态改变时触发的回调函数 */
onChange?: (affixed?: boolean) => void;
/** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
target?: () => Window | HTMLElement;
target?: () => Window | HTMLElement | null;
prefixCls?: string;
}
export default class Affix extends React.Component<AffixProps, any> {
export interface AffixState {
affixStyle: React.CSSProperties | undefined;
placeholderStyle: React.CSSProperties | undefined;
}
export default class Affix extends React.Component<AffixProps, AffixState> {
static propTypes = {
offsetTop: PropTypes.number,
offsetBottom: PropTypes.number,
@ -69,9 +74,6 @@ export default class Affix extends React.Component<AffixProps, any> {
scrollEvent: any;
resizeEvent: any;
timeout: any;
refs: {
fixedNode: HTMLElement;
};
events = [
'resize',
@ -83,17 +85,18 @@ export default class Affix extends React.Component<AffixProps, any> {
'load',
];
eventHandlers = {};
eventHandlers: {
[key: string]: any;
} = {};
constructor(props) {
super(props);
this.state = {
affixStyle: null,
placeholderStyle: null,
};
}
state: AffixState = {
affixStyle: undefined,
placeholderStyle: undefined,
};
setAffixStyle(e, affixStyle) {
private fixedNode: HTMLElement;
setAffixStyle(e: any, affixStyle: React.CSSProperties | null) {
const { onChange = noop, target = getDefaultTarget } = this.props;
const originalAffixStyle = this.state.affixStyle;
const isWindow = target() === window;
@ -103,7 +106,7 @@ export default class Affix extends React.Component<AffixProps, any> {
if (shallowequal(affixStyle, originalAffixStyle)) {
return;
}
this.setState({ affixStyle }, () => {
this.setState({ affixStyle: affixStyle as React.CSSProperties }, () => {
const affixed = !!this.state.affixStyle;
if ((affixStyle && !originalAffixStyle) ||
(!affixStyle && originalAffixStyle)) {
@ -112,16 +115,16 @@ export default class Affix extends React.Component<AffixProps, any> {
});
}
setPlaceholderStyle(placeholderStyle) {
setPlaceholderStyle(placeholderStyle: React.CSSProperties | null) {
const originalPlaceholderStyle = this.state.placeholderStyle;
if (shallowequal(placeholderStyle, originalPlaceholderStyle)) {
return;
}
this.setState({ placeholderStyle });
this.setState({ placeholderStyle: placeholderStyle as React.CSSProperties });
}
@throttleByAnimationFrameDecorator()
updatePosition(e) {
updatePosition(e: any) {
let { offsetTop, offsetBottom, offset, target = getDefaultTarget } = this.props;
const targetNode = target();
@ -131,8 +134,8 @@ export default class Affix extends React.Component<AffixProps, any> {
const affixNode = ReactDOM.findDOMNode(this) as HTMLElement;
const elemOffset = getOffset(affixNode, targetNode);
const elemSize = {
width: this.refs.fixedNode.offsetWidth,
height: this.refs.fixedNode.offsetHeight,
width: this.fixedNode.offsetWidth,
height: this.fixedNode.offsetHeight,
};
const offsetMode = {
@ -200,10 +203,10 @@ export default class Affix extends React.Component<AffixProps, any> {
});
}
componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps: AffixProps) {
if (this.props.target !== nextProps.target) {
this.clearEventListeners();
this.setTargetEventListeners(nextProps.target);
this.setTargetEventListeners(nextProps.target!);
// Mock Event object.
this.updatePosition({});
@ -216,7 +219,7 @@ export default class Affix extends React.Component<AffixProps, any> {
(this.updatePosition as any).cancel();
}
setTargetEventListeners(getTarget) {
setTargetEventListeners(getTarget: () => HTMLElement | Window | null) {
const target = getTarget();
if (!target) {
return;
@ -237,6 +240,10 @@ export default class Affix extends React.Component<AffixProps, any> {
});
}
saveFixedNode = (node: HTMLDivElement) => {
this.fixedNode = node;
}
render() {
const className = classNames({
[this.props.prefixCls || 'ant-affix']: this.state.affixStyle,
@ -246,7 +253,7 @@ export default class Affix extends React.Component<AffixProps, any> {
const placeholderStyle = { ...this.state.placeholderStyle, ...this.props.style };
return (
<div {...props} style={placeholderStyle}>
<div className={className} ref="fixedNode" style={this.state.affixStyle}>
<div className={className} ref={this.saveFixedNode} style={this.state.affixStyle}>
{this.props.children}
</div>
</div>

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Animate from 'rc-animate';
import Icon from '../icon';
import classNames from 'classnames';

View File

@ -1,4 +1,5 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@alert-prefix-cls: ~"@{ant-prefix}-alert";
@ -6,75 +7,71 @@
@alert-text-color: @text-color;
.@{alert-prefix-cls} {
.reset-component;
position: relative;
padding: 8px 48px 8px 38px;
padding: 8px 15px 8px 37px;
border-radius: @border-radius-base;
color: @alert-text-color;
font-size: @font-size-base;
line-height: @line-height-base;
&&-no-icon {
padding: 8px 48px 8px 16px;
padding: 8px 15px;
}
&-icon {
font-size: @font-size-lg;
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-lg / 2;
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2 + 1px;
left: 16px;
position: absolute;
}
&-description {
font-size: @font-size-base;
line-height: 21px;
line-height: 22px;
display: none;
}
&-success {
border: @border-width-base @border-style-base @green-2;
background-color: @green-1;
border: @border-width-base @border-style-base ~`colorPalette("@{success-color}", 3)`;
background-color: ~`colorPalette("@{success-color}", 1)`;
.@{alert-prefix-cls}-icon {
color: @success-color;
}
}
&-info {
border: @border-width-base @border-style-base @primary-2;
background-color: @primary-1;
border: @border-width-base @border-style-base ~`colorPalette("@{info-color}", 3)`;
background-color: ~`colorPalette("@{info-color}", 1)`;
.@{alert-prefix-cls}-icon {
color: @info-color;
}
}
&-warning {
border: @border-width-base @border-style-base @yellow-2;
background-color: @yellow-1;
border: @border-width-base @border-style-base ~`colorPalette("@{warning-color}", 3)`;
background-color: ~`colorPalette("@{warning-color}", 1)`;
.@{alert-prefix-cls}-icon {
color: @warning-color;
}
}
&-error {
border: @border-width-base @border-style-base @red-2;
background-color: @red-1;
border: @border-width-base @border-style-base ~`colorPalette("@{error-color}", 3)`;
background-color: ~`colorPalette("@{error-color}", 1)`;
.@{alert-prefix-cls}-icon {
color: @error-color;
}
}
&-close-icon {
font-size: @font-size-base;
font-size: @font-size-sm;
position: absolute;
right: 16px;
top: 10px;
height: 12px;
line-height: 12px;
top: 8px;
line-height: 22px;
overflow: hidden;
cursor: pointer;
.@{iconfont-css-prefix}-cross {
color: @text-color-secondary;
transition: color .3s ease;
transition: color .3s;
&:hover {
color: #404040;
}
@ -87,21 +84,21 @@
}
&-with-description {
padding: 16px 16px 16px 60px;
padding: 15px 15px 15px 64px;
position: relative;
border-radius: @border-radius-base;
color: @text-color;
line-height: 1.5;
line-height: @line-height-base;
}
&-with-description&-no-icon {
padding: 16px;
padding: 15px;
}
&-with-description &-icon {
position: absolute;
top: 16px;
left: 20px;
left: 24px;
font-size: 24px;
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
@ -42,7 +42,7 @@ function easeInOutCubic(t: number, b: number, c: number, d: number) {
}
const reqAnimFrame = getRequestAnimationFrame();
function scrollTo(href: string, offsetTop = 0, target, callback = () => { }) {
function scrollTo(href: string, offsetTop = 0, target: () => Window | HTMLElement, callback = () => { }) {
const scrollTop = getScroll(target(), true);
const targetElement = document.getElementById(href.substring(1));
if (!targetElement) {
@ -95,9 +95,7 @@ export default class Anchor extends React.Component<AnchorProps, any> {
antAnchor: PropTypes.object,
};
refs: {
ink?: any;
};
private inkNode: HTMLSpanElement;
private links: String[];
private scrollEvent: any;
@ -157,7 +155,7 @@ export default class Anchor extends React.Component<AnchorProps, any> {
});
}
handleScrollTo = (link) => {
handleScrollTo = (link: string) => {
const { offsetTop, target = getDefaultTarget } = this.props;
this.animating = true;
this.setState({ activeLink: link });
@ -198,10 +196,14 @@ export default class Anchor extends React.Component<AnchorProps, any> {
const { prefixCls } = this.props;
const linkNode = ReactDOM.findDOMNode(this as any).getElementsByClassName(`${prefixCls}-link-title-active`)[0];
if (linkNode) {
this.refs.ink.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
}
}
saveInkNode = (node: HTMLSpanElement) => {
this.inkNode = node;
}
render() {
const {
prefixCls,
@ -228,7 +230,7 @@ export default class Anchor extends React.Component<AnchorProps, any> {
<div className={wrapperClass} style={style}>
<div className={anchorClass}>
<div className={`${prefixCls}-ink`} >
<span className={inkClass} ref="ink" />
<span className={inkClass} ref={this.saveInkNode} />
</div>
{children}
</div>

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

View File

@ -14,7 +14,7 @@ describe('Anchor Render', () => {
wrapper.find('a[href="#API"]').simulate('click');
wrapper.node.handleScroll();
expect(wrapper.node.state).not.toBe(null);
wrapper.instance().handleScroll();
expect(wrapper.instance().state).not.toBe(null);
});
});

View File

@ -79,7 +79,7 @@ exports[`renders ./components/anchor/demo/basic.md correctly 1`] = `
</div>
`;
exports[`renders ./components/anchor/demo/fixed.md correctly 1`] = `
exports[`renders ./components/anchor/demo/static.md correctly 1`] = `
<div
class="ant-anchor-wrapper"
>

View File

@ -1,8 +1,8 @@
---
order: 2
title:
zh-CN: 固定
en-US: Fixed Anchor
zh-CN: 静态位置
en-US: Static Anchor
---
## zh-CN

View File

@ -1,5 +1,8 @@
import Anchor from './Anchor';
import AnchorLink from './AnchorLink';
export { AnchorProps } from './Anchor';
export { AnchorLinkProps } from './AnchorLink';
Anchor.Link = AnchorLink;
export default Anchor;

View File

@ -1,7 +1,12 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@anchor-border-width: 2px;
.@{ant-prefix}-anchor {
.reset-component;
position: relative;
padding-left: @anchor-border-width;
&-wrapper {
background-color: @component-background;
}
@ -14,7 +19,7 @@
&:before {
content: ' ';
position: relative;
width: 2px;
width: @anchor-border-width;
height: 100%;
display: block;
background-color: @border-color-split;
@ -23,10 +28,10 @@
&-ball {
display: none;
position: absolute;
width: 9px;
height: 9px;
border-radius: 9px;
border: 3px solid @primary-color;
width: 8px;
height: 8px;
border-radius: 8px;
border: 2px solid @primary-color;
background-color: @component-background;
left: 50%;
transition: top .3s ease-in-out;
@ -42,7 +47,7 @@
}
&-link {
padding: 8px 0 8px 18px;
padding: 8px 0 8px 16px;
line-height: 1;
&-title {

View File

@ -1,18 +1,22 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
export default class InputElement extends React.Component<any, any> {
export interface InputElementProps {
children: React.ReactElement<any>;
}
export default class InputElement extends React.Component<InputElementProps, any> {
private ele: HTMLInputElement;
focus = () => {
this.ele.focus ? this.ele.focus() : (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() : (findDOMNode(this.ele) as HTMLInputElement).blur();
this.ele.blur ? this.ele.blur() : (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).blur();
}
saveRef = (ele: HTMLInputElement) => {
this.ele = ele;
const childRef = this.props.children.ref;
const { ref: childRef } = this.props.children as any;
if (typeof childRef === 'function') {
childRef(ele);
}

View File

@ -3,7 +3,7 @@
exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px;"
style="width:200px"
>
<div
aria-autocomplete="list"
@ -18,7 +18,7 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none;"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
input here
@ -46,7 +46,7 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none;"
style="user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
<b />
@ -58,11 +58,11 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1`] = `
<div
class="certain-category-search-wrapper"
style="width:250px;"
style="width:250px"
>
<div
class="ant-select-lg ant-select-lg certain-category-search ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:100%;"
style="width:100%"
>
<div
aria-autocomplete="list"
@ -77,7 +77,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none;"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
input here
@ -116,7 +116,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none;"
style="user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
<b />
@ -129,7 +129,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px;"
style="width:200px"
>
<div
aria-autocomplete="list"
@ -152,7 +152,7 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
<textarea
class="ant-input custom ant-select-search__field"
placeholder="input here"
style="height:50px;"
style="height:50px"
/>
<span
class="ant-select-search__field__mirror"
@ -165,7 +165,7 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none;"
style="user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
<b />
@ -177,7 +177,7 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px;"
style="width:200px"
>
<div
aria-autocomplete="list"
@ -192,7 +192,7 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none;"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
try to type \`b\`
@ -220,7 +220,7 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none;"
style="user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
<b />
@ -232,7 +232,7 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px;"
style="width:200px"
>
<div
aria-autocomplete="list"
@ -247,7 +247,7 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none;"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
input here
@ -275,7 +275,7 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none;"
style="user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
<b />
@ -287,11 +287,11 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly 1`] = `
<div
class="global-search-wrapper"
style="width:300px;"
style="width:300px"
>
<div
class="ant-select-lg ant-select-lg global-search ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:100%;"
style="width:100%"
>
<div
aria-autocomplete="list"
@ -306,7 +306,7 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none;"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
input here
@ -350,7 +350,7 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none;"
style="user-select:none;-webkit-user-select:none"
unselectable="unselectable"
>
<b />

View File

@ -1,8 +1,11 @@
import React from 'react';
import { mount } from 'enzyme';
import AutoComplete from '..';
import focusTest from '../../../tests/shared/focusTest';
describe('AutoComplete with Custom Input Element Render', () => {
focusTest(AutoComplete);
it('AutoComplete with custom Input render perfectly', () => {
const wrapper = mount(
<AutoComplete dataSource={['12345', '23456', '34567']}>
@ -12,7 +15,7 @@ describe('AutoComplete with Custom Input Element Render', () => {
expect(wrapper.find('textarea').length).toBe(1);
wrapper.find('textarea').simulate('change', { target: { value: '123' } });
const dropdownWrapper = mount(wrapper.find('Trigger').node.getComponent());
const dropdownWrapper = mount(wrapper.find('Trigger').instance().getComponent());
// should not filter data source defaultly
expect(dropdownWrapper.find('MenuItem').length).toBe(3);

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { Option, OptGroup } from 'rc-select';
import classNames from 'classnames';
import Select, { AbstractSelectProps, SelectValue, OptionProps, OptGroupProps } from '../select';
@ -34,7 +34,7 @@ function isSelectOptionOrSelectOptGroup(child: any): Boolean {
return child && child.type && (child.type.isSelectOption || child.type.isSelectOptGroup);
}
export default class AutoComplete extends React.Component<AutoCompleteProps, any> {
export default class AutoComplete extends React.Component<AutoCompleteProps, {}> {
static Option = Option as React.ClassicComponentClass<OptionProps>;
static OptGroup = OptGroup as React.ClassicComponentClass<OptGroupProps>;
@ -47,6 +47,8 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, any
filterOption: false,
};
private select: any;
getInputElement = () => {
const { children } = this.props;
const element = children && React.isValidElement(children) && children.type !== Option ?
@ -59,6 +61,18 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, any
);
}
focus() {
this.select.focus();
}
blur() {
this.select.blur();
}
saveSelect = (node: any) => {
this.select = node;
}
render() {
let {
size, className = '', notFoundContent, prefixCls, optionLabelProp, dataSource, children,
@ -106,6 +120,7 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, any
optionLabelProp={optionLabelProp}
getInputElement={this.getInputElement}
notFoundContent={notFoundContent}
ref={this.saveSelect}
>
{options}
</Select>

View File

@ -7,6 +7,8 @@
@autocomplete-prefix-cls: ~"@{select-prefix-cls}-auto-complete";
.@{autocomplete-prefix-cls} {
.reset-component;
&.@{select-prefix-cls} {
.@{select-prefix-cls} {
&-selection {

View File

@ -3,7 +3,7 @@
exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
<div>
<span
style="margin-right:24px;"
style="margin-right:24px"
>
<span
class="ant-badge"
@ -22,7 +22,7 @@ exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1100%);-webkit-transform:translateY(-1100%);transform:translateY(-1100%);"
style="transition:none;-ms-transform:translateY(-1100%);-webkit-transform:translateY(-1100%);transform:translateY(-1100%)"
>
<p
class=""
@ -253,7 +253,7 @@ exports[`renders ./components/avatar/demo/dynamic.md correctly 1`] = `
<div>
<span
class="ant-avatar ant-avatar-lg ant-avatar-circle"
style="background-color:#f56a00;"
style="background-color:#f56a00;vertical-align:middle"
>
<span
class="ant-avatar-string"
@ -263,7 +263,7 @@ exports[`renders ./components/avatar/demo/dynamic.md correctly 1`] = `
</span>
<button
class="ant-btn ant-btn-sm"
style="margin-left:16px;"
style="margin-left:16px;vertical-align:middle"
type="button"
>
<span>
@ -309,7 +309,7 @@ exports[`renders ./components/avatar/demo/type.md correctly 1`] = `
</span>
<span
class="ant-avatar ant-avatar-circle"
style="color:#f56a00;background-color:#fde3cf;"
style="color:#f56a00;background-color:#fde3cf"
>
<span
class="ant-avatar-string"
@ -319,7 +319,7 @@ exports[`renders ./components/avatar/demo/type.md correctly 1`] = `
</span>
<span
class="ant-avatar ant-avatar-circle ant-avatar-icon"
style="background-color:#87d068;"
style="background-color:#87d068"
>
<i
class="anticon anticon-user"

View File

@ -37,8 +37,12 @@ class Autoset extends React.Component {
render() {
return (
<div>
<Avatar style={{ backgroundColor: this.state.color }} size="large">{this.state.user}</Avatar>
<Button size="small" style={{ marginLeft: 16 }} onClick={this.changeUser}>Change</Button>
<Avatar style={{ backgroundColor: this.state.color, verticalAlign: 'middle' }} size="large">
{this.state.user}
</Avatar>
<Button size="small" style={{ marginLeft: 16, verticalAlign: 'middle' }} onClick={this.changeUser}>
Change
</Button>
</div>
);
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Icon from '../icon';
import classNames from 'classnames';

View File

@ -1,8 +1,10 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@avatar-prefix-cls: ~"@{ant-prefix}-avatar";
.@{avatar-prefix-cls} {
.reset-component;
display: inline-block;
text-align: center;
background: @avatar-bg;

View File

@ -4,7 +4,7 @@ exports[`renders ./components/back-top/demo/basic.md correctly 1`] = `
<div>
Scroll down to see the bottom-right
<strong
style="color:rgba(64, 64, 64, 0.6);"
style="color:rgba(64, 64, 64, 0.6)"
>
gray
</strong>
@ -16,7 +16,7 @@ exports[`renders ./components/back-top/demo/custom.md correctly 1`] = `
<div>
Scroll down to see the bottom-right
<strong
style="color:#1088e9;"
style="color:#1088e9"
>
blue
</strong>

View File

@ -15,7 +15,7 @@ describe('BackTop', () => {
const wrapper = mount(<BackTop visibilityHeight={-1} />);
document.documentElement.scrollTop = 400;
// trigger scroll manually
wrapper.node.handleScroll();
wrapper.instance().handleScroll();
jest.runAllTimers();
wrapper.find('.ant-back-top').simulate('click');
jest.runAllTimers();

View File

@ -1,9 +1,8 @@
import React from 'react';
import * as React from 'react';
import Animate from 'rc-animate';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import classNames from 'classnames';
import omit from 'omit.js';
import Icon from '../icon';
import getScroll from '../_util/getScroll';
import getRequestAnimationFrame from '../_util/getRequestAnimationFrame';
@ -109,7 +108,7 @@ export default class BackTop extends React.Component<BackTopProps, any> {
const defaultElement = (
<div className={`${prefixCls}-content`}>
<Icon className={`${prefixCls}-icon`} type="to-top" />
<div className={`${prefixCls}-icon`} />
</div>
);

View File

@ -1,8 +1,10 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@backtop-prefix-cls: ~"@{ant-prefix}-back-top";
.@{backtop-prefix-cls} {
.reset-component;
z-index: @zindex-back-top;
position: fixed;
right: 100px;
@ -19,6 +21,7 @@
color: @back-top-color;
text-align: center;
transition: all .3s @ease-in-out;
overflow: hidden;
&:hover {
background-color: @back-top-hover-bg;
@ -27,7 +30,9 @@
}
&-icon {
font-size: 20px;
margin-top: 10px;
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;
}
}

View File

@ -1,9 +1,9 @@
import React from 'react';
import * as React from 'react';
import { createElement, Component } from 'react';
import omit from 'omit.js';
import classNames from 'classnames';
function getNumberArray(num) {
function getNumberArray(num: string | number | undefined) {
return num ?
num.toString()
.split('')
@ -21,7 +21,12 @@ export interface ScrollNumberProps {
title?: string | number;
}
export default class ScrollNumber extends Component<ScrollNumberProps, any> {
export interface ScrollNumberState {
animateStarted?: boolean;
count?: string | number;
}
export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNumberState> {
static defaultProps = {
prefixCls: 'ant-scroll-number',
count: null,
@ -31,7 +36,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, any> {
lastCount: any;
constructor(props) {
constructor(props: ScrollNumberProps) {
super(props);
this.state = {
animateStarted: true,
@ -39,14 +44,14 @@ export default class ScrollNumber extends Component<ScrollNumberProps, any> {
};
}
getPositionByNum(num, i) {
getPositionByNum(num: number, i: number) {
if (this.state.animateStarted) {
return 10 + num;
}
const currentDigit = getNumberArray(this.state.count)[i];
const lastDigit = getNumberArray(this.lastCount)[i];
// 同方向则在同一侧切换数字
if (this.state.count > this.lastCount) {
if (this.state.count! > this.lastCount) {
if (currentDigit >= lastDigit) {
return 10 + num;
}
@ -58,7 +63,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, any> {
return num;
}
componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps: ScrollNumberProps) {
if ('count' in nextProps) {
if (this.state.count === nextProps.count) {
return;
@ -85,7 +90,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, any> {
}
}
renderNumberList(position) {
renderNumberList(position: number) {
const childrenToReturn: React.ReactElement<any>[] = [];
for (let i = 0; i < 30; i++) {
const currentClassName = (position === i) ? 'current' : '';
@ -94,7 +99,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, any> {
return childrenToReturn;
}
renderCurrentNumber(num, i) {
renderCurrentNumber(num: number, i: number) {
const position = this.getPositionByNum(num, i);
const removeTransition = this.state.animateStarted ||
(getNumberArray(this.lastCount)[i] === undefined);
@ -112,7 +117,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, any> {
renderNumberElement() {
const state = this.state;
if (!state.count || isNaN(state.count)) {
if (!state.count || isNaN(state.count as number)) {
return state.count;
}
return getNumberArray(state.count)

View File

@ -16,7 +16,7 @@ exports[`renders ./components/badge/demo/basic.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
>
<p
class=""
@ -206,7 +206,7 @@ exports[`renders ./components/badge/demo/change.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
>
<p
class=""
@ -383,7 +383,7 @@ exports[`renders ./components/badge/demo/change.md correctly 1`] = `
</div>
</div>
<div
style="margin-top:10px;"
style="margin-top:10px"
>
<span
class="ant-badge"
@ -456,7 +456,7 @@ exports[`renders ./components/badge/demo/link.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
>
<p
class=""
@ -620,13 +620,13 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
data-show="true"
title="25"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1200%);-webkit-transform:translateY(-1200%);transform:translateY(-1200%);"
style="transition:none;-ms-transform:translateY(-1200%);-webkit-transform:translateY(-1200%);transform:translateY(-1200%)"
>
<p
class=""
@ -781,7 +781,7 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
</span>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
>
<p
class=""
@ -942,12 +942,12 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
<sup
class="ant-scroll-number ant-badge-count"
data-show="true"
style="background-color:#fff;color:#999;box-shadow:0 0 0 1px #d9d9d9 inset;"
style="background-color:#fff;color:#999;box-shadow:0 0 0 1px #d9d9d9 inset"
title="4"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1400%);-webkit-transform:translateY(-1400%);transform:translateY(-1400%);"
style="transition:none;-ms-transform:translateY(-1400%);-webkit-transform:translateY(-1400%);transform:translateY(-1400%)"
>
<p
class=""
@ -1106,9 +1106,9 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
data-show="true"
style="background-color:#87d068;"
style="background-color:#52c41a"
title="109"
>
99+
@ -1127,13 +1127,13 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
data-show="true"
title="99"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%);"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%)"
>
<p
class=""
@ -1288,7 +1288,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
</span>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%);"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%)"
>
<p
class=""
@ -1451,7 +1451,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
data-show="true"
title="100"
>
@ -1466,7 +1466,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
data-show="true"
title="99"
>
@ -1481,7 +1481,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
data-show="true"
title="1000"
>

View File

@ -30,12 +30,12 @@ ReactDOM.render(
<style>
.ant-badge:not(.ant-badge-status) {
margin-right: 16px;
margin-right: 20px;
}
.head-example {
width: 42px;
height: 42px;
border-radius: 6px;
border-radius: 4px;
background: #eee;
display: inline-block;
}

View File

@ -22,7 +22,7 @@ ReactDOM.render(
<div>
<Badge count={25} />
<Badge count={4} style={{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset' }} />
<Badge count={109} style={{ backgroundColor: '#87d068' }} />
<Badge count={109} style={{ backgroundColor: '#52c41a' }} />
</div>
, mountNode);
````

View File

@ -30,3 +30,4 @@ Badge normally appears in proximity to notifications or user avatars with eye-ca
| showZero | Whether to show badge when `count` is zero | boolean | `false` |
| status | Set Badge as a status dot | `success` \| `processing` \| `default` \| `error` \| `warning` | `''` |
| text | If `status` is set, `text` sets the display text of the status `dot` | string | `''` |
| offset | set offset of the badge dot, like [x, y] | [number, number] | - |

View File

@ -1,9 +1,10 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import Animate from 'rc-animate';
import ScrollNumber from './ScrollNumber';
import classNames from 'classnames';
import warning from '../_util/warning';
export { ScrollNumberProps } from './ScrollNumber';
export interface BadgeProps {
/** Number to show in badge */
@ -19,6 +20,7 @@ export interface BadgeProps {
className?: string;
status?: 'success' | 'processing' | 'default' | 'error' | 'warning';
text?: string;
offset?: [number | string, number | string];
}
export default class Badge extends React.Component<BadgeProps, any> {
@ -54,6 +56,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
dot,
status,
text,
offset,
...restProps,
} = this.props;
const isDot = dot || status;
@ -66,27 +69,29 @@ export default class Badge extends React.Component<BadgeProps, any> {
const isZero = displayCount === '0' || displayCount === 0;
const isEmpty = displayCount === null || displayCount === undefined || displayCount === '';
const hidden = (isEmpty || (isZero && !showZero)) && !isDot;
const statusCls = classNames({
[`${prefixCls}-status-dot`]: !!status,
[`${prefixCls}-status-${status}`]: !!status,
});
const scrollNumberCls = classNames({
[`${prefixCls}-dot`]: isDot,
[`${prefixCls}-count`]: !isDot,
[`${prefixCls}-multiple-words`]: count && count.toString && count.toString().length > 1,
[`${prefixCls}-status-${status}`]: !!status,
});
const badgeCls = classNames(className, prefixCls, {
[`${prefixCls}-status`]: !!status,
[`${prefixCls}-not-a-wrapper`]: !children,
});
warning(
!(children && status),
'`Badge[children]` and `Badge[status]` cannot be used at the same time.',
);
const styleWithOffset = offset ? {
marginTop: offset[0],
marginLeft: offset[1],
...style,
} : style;
// <Badge status="success" />
if (!children && status) {
const statusCls = classNames({
[`${prefixCls}-status-dot`]: !!status,
[`${prefixCls}-status-${status}`]: true,
});
return (
<span className={badgeCls}>
<span className={badgeCls} style={styleWithOffset}>
<span className={statusCls} />
<span className={`${prefixCls}-status-text`}>{text}</span>
</span>
@ -100,7 +105,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
className={scrollNumberCls}
count={displayCount}
title={count}
style={style}
style={styleWithOffset}
/>
);

View File

@ -31,3 +31,4 @@ title: Badge
| showZero | 当数值为 0 时,是否展示 Badge | boolean | false |
| status | 设置 Badge 为状态点 | Enum{ 'success', 'processing, 'default', 'error', 'warning' } | '' |
| text | 在设置了 `status` 的前提下有效,设置状态点的文本 | string | '' |
| offset | 设置状态点的位置偏移,格式为 [x, y] | [number, number] | - |

View File

@ -1,9 +1,11 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@badge-prefix-cls: ~"@{ant-prefix}-badge";
@number-prefix-cls: ~"@{ant-prefix}-scroll-number";
.@{badge-prefix-cls} {
.reset-component;
position: relative;
display: inline-block;
line-height: 1;
@ -24,13 +26,17 @@
font-size: @badge-font-size;
white-space: nowrap;
transform-origin: -10% center;
font-family: tahoma;
box-shadow: 0 0 0 1px #fff;
a,
a:hover {
color: #fff;
}
}
&-multiple-words {
padding: 0 8px;
}
&-dot {
position: absolute;
transform: translateX(-50%);
@ -49,16 +55,19 @@
vertical-align: baseline;
&-dot {
width: 8px;
height: 8px;
width: @badge-status-size;
height: @badge-status-size;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
position: relative;
top: -1px;
}
&-success {
background-color: @success-color;
}
&-processing {
background-color: @primary-color;
background-color: @processing-color;
position: relative;
&:after {
position: absolute;
@ -67,7 +76,7 @@
width: 100%;
height: 100%;
border-radius: 50%;
border: 1px solid @primary-color;
border: 1px solid @processing-color;
content: '';
animation: antStatusProcessing 1.2s infinite ease-in-out;
}
@ -99,7 +108,7 @@
animation-fill-mode: both;
}
&-not-a-wrapper &-count {
&-not-a-wrapper .@{ant-prefix}-scroll-number {
top: auto;
display: block;
position: relative;
@ -126,6 +135,7 @@
height: @badge-height;
> p {
height: @badge-height;
margin: 0;
}
}
}

View File

@ -1,21 +1,26 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import { cloneElement } from 'react';
import warning from '../_util/warning';
import BreadcrumbItem from './BreadcrumbItem';
import classNames from 'classnames';
export interface Route {
path: string;
breadcrumbName: string;
}
export interface BreadcrumbProps {
prefixCls?: string;
routes?: Array<any>;
params?: Object;
routes?: Route[];
params?: any;
separator?: React.ReactNode;
itemRender?: (route: any, params: any, routes: Array<any>, paths: Array<string>) => React.ReactNode;
style?: React.CSSProperties;
className?: string;
}
function getBreadcrumbName(route, params) {
function getBreadcrumbName(route: Route, params: any) {
if (!route.breadcrumbName) {
return null;
}
@ -27,7 +32,7 @@ function getBreadcrumbName(route, params) {
return name;
}
function defaultItemRender(route, params, routes, paths) {
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

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
export interface BreadcrumbItemProps {

View File

@ -1,11 +1,3 @@
import { render } from 'enzyme';
import demoTest from '../../../tests/shared/demoTest';
import routerDemo from '../demo/router.md';
demoTest('breadcrumb', { skip: ['router.md', 'router-4.md'] });
const testMethod = typeof window !== 'undefined' ? test : test.skip;
testMethod('renders ./components/breadcrumb/demo/router.md correctly', () => {
const wrapper = render(routerDemo);
expect(wrapper).toMatchSnapshot();
});

View File

@ -1,5 +1,8 @@
import Breadcrumb from './Breadcrumb';
import BreadcrumbItem from './BreadcrumbItem';
export { BreadcrumbProps } from './Breadcrumb';
export { BreadcrumbItemProps } from './BreadcrumbItem';
Breadcrumb.Item = BreadcrumbItem;
export default Breadcrumb;

View File

@ -1,13 +1,18 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@breadcrumb-prefix-cls: ~"@{ant-prefix}-breadcrumb";
.@{breadcrumb-prefix-cls} {
color: @text-color;
font-size: @font-size-base;
.reset-component;
color: @text-color-secondary;
.@{iconfont-css-prefix} {
font-size: @font-size-sm;
}
a {
color: @text-color;
color: @text-color-secondary;
transition: color .3s;
&:hover {
color: @primary-5;
@ -15,7 +20,6 @@
}
& > span:last-child {
font-weight: 500;
color: @text-color;
}
@ -24,8 +28,8 @@
}
&-separator {
margin: 0 8px;
color: rgba(0, 0, 0, 0.3);
margin: 0 @padding-xs;
color: @text-color-secondary;
}
&-link {

View File

@ -251,7 +251,7 @@ exports[`renders ./components/button/demo/disabled.md correctly 1`] = `
exports[`renders ./components/button/demo/ghost.md correctly 1`] = `
<div
style="background:rgb(190, 200, 200);padding:26px 16px 16px;"
style="background:rgb(190, 200, 200);padding:26px 16px 16px"
>
<button
class="ant-btn ant-btn-primary ant-btn-background-ghost"

View File

@ -49,3 +49,15 @@ exports[`Button renders correctly 1`] = `
</span>
</button>
`;
exports[`Button should support link button 1`] = `
<a
class="ant-btn"
href="http://ant.design"
target="_blank"
>
<span>
link button
</span>
</a>
`;

View File

@ -52,7 +52,7 @@ describe('Button', () => {
<DefaultButton />
);
wrapper.simulate('click');
expect(wrapper.hasClass('ant-btn-loading')).toBe(true);
expect(wrapper.find('.ant-btn-loading').length).toBe(1);
});
it('should change loading state with delay', () => {
@ -74,4 +74,11 @@ describe('Button', () => {
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>
);
expect(wrapper.render()).toMatchSnapshot();
});
});

View File

@ -1,7 +1,6 @@
import React from 'react';
import * as React from 'react';
import classNames from 'classnames';
export type ButtonSize = 'small' | 'large';
import { ButtonSize } from './button';
export interface ButtonGroupProps {
size?: ButtonSize;

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import omit from 'omit.js';
@ -52,6 +52,9 @@ export interface ButtonProps {
prefixCls?: string;
className?: string;
ghost?: boolean;
target?: string;
href?: string;
download?: string;
}
export default class Button extends React.Component<ButtonProps, any> {
@ -95,7 +98,7 @@ export default class Button extends React.Component<ButtonProps, any> {
}
if (typeof loading !== 'boolean' && loading && loading.delay) {
this.delayTimeout = setTimeout(() => this.setState({ loading }), loading.delay);
this.delayTimeout = window.setTimeout(() => this.setState({ loading }), loading.delay);
} else {
this.setState({ loading });
}
@ -114,7 +117,7 @@ export default class Button extends React.Component<ButtonProps, any> {
// Add click effect
this.setState({ clicked: true });
clearTimeout(this.timeout);
this.timeout = setTimeout(() => this.setState({ clicked: false }), 500);
this.timeout = window.setTimeout(() => this.setState({ clicked: false }), 500);
const onClick = this.props.onClick;
if (onClick) {
@ -142,6 +145,8 @@ export default class Button extends React.Component<ButtonProps, any> {
break;
}
const ComponentProp = others.href ? 'a' : 'button';
const classes = classNames(prefixCls, className, {
[`${prefixCls}-${type}`]: type,
[`${prefixCls}-${shape}`]: shape,
@ -155,17 +160,17 @@ export default class Button extends React.Component<ButtonProps, any> {
const iconType = loading ? 'loading' : icon;
const iconNode = iconType ? <Icon type={iconType} /> : null;
const needInserted = React.Children.count(children) === 1 && (!iconType || iconType === 'loading');
const kids = React.Children.map(children, child => insertSpace(child, needInserted));
const kids = children ? React.Children.map(children, child => insertSpace(child, needInserted)) : null;
return (
<button
<ComponentProp
{...omit(others, ['loading'])}
type={htmlType || 'button'}
type={others.href ? undefined : (htmlType || 'button')}
className={classes}
onClick={this.handleClick}
>
{iconNode}{kids}
</button>
</ComponentProp>
);
}
}

View File

@ -24,9 +24,13 @@ To get a customized button, just set `type`/`shape`/`size`/`loading`/`disabled`.
| size | can be set to `small` `large` or omitted | string | `default` |
| type | can be set to `primary` `ghost` `dashed` `danger`(added in 2.7) or omitted (meaning `default`) | string | `default` |
| onClick | set the handler to handle `click` event | function | - |
| href | redirect url of link button | string | - |
| target | same as target attribute of a, works when href is specified | string | - |
`<Button>Hello world!</Button>` will be rendered into `<button><span>Hello world!</span></button>`, and all the properties which are not listed above will be transferred to the `<button>` tag.
`<Button href="http://example.com">Hello world!</Button>` will be rendered into `<a href="http://example.com"><span>Hello world!</span></a>`.
<style>
[id^=components-button-demo-] .ant-btn {
margin-right: 8px;

View File

@ -1,5 +1,8 @@
import Button from './button';
import ButtonGroup from './button-group';
export { ButtonProps, ButtonShape, ButtonSize, ButtonType } from './button';
export { ButtonGroupProps } from './button-group';
Button.Group = ButtonGroup;
export default Button;

View File

@ -27,9 +27,14 @@ subtitle: 按钮
| size | 设置按钮大小,可选值为 `small` `large` 或者不设 | string | `default` |
| type | 设置按钮类型,可选值为 `primary` `dashed` `danger`(版本 2.7 中增加) 或者不设 | string | - |
| onClick | `click` 事件的 handler | function | - |
| href | 点击跳转的地址,指定此属性 button 的行为和 a 链接一致 | string | - |
| target | 相当于 a 链接的 target 属性href 存在时生效 | string | - |
`<Button>Hello world!</Button>` 最终会被渲染为 `<button><span>Hello world!</span></button>`,并且除了上表中的属性,其它属性都会直接传到 `<button></button>`
`<Button href="http://example.com">Hello world!</Button>` 则会渲染为 `<a href="http://example.com"><span>Hello world!</span></a>`
<style>
[id^="components-button-demo-"] .ant-btn {
margin-right: 8px;

View File

@ -12,6 +12,7 @@
// Button styles
// -----------------------------
.@{btn-prefix-cls} {
.reset-component;
.btn;
.btn-default;
@ -127,7 +128,7 @@
// To ensure that a space will be placed between character and `Icon`.
> .@{iconfont-css-prefix} + span,
> span + .@{iconfont-css-prefix} {
margin-left: 0.5em;
margin-left: 8px;
}
&-clicked:after {
@ -173,3 +174,13 @@
border-width: 6px;
}
}
a.@{btn-prefix-cls} {
line-height: @btn-height-base - 2px;
&-lg {
line-height: @btn-height-lg - 2px;
}
&-sm {
line-height: @btn-height-sm - 2px;
}
}

View File

@ -40,7 +40,7 @@
&:hover,
&:focus {
.button-color(@primary-color; @background; @primary-color);
.button-color(@primary-5; @background; @primary-5);
}
&:active,
@ -56,12 +56,12 @@
&:hover,
&:focus {
.button-color(@btn-primary-color; @color; @color;);
.button-color(@btn-primary-color; ~`colorPalette("@{color}", 5)`; ~`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();
@ -185,7 +185,7 @@
}
&-sm {
.button-size(@btn-height-sm; @btn-padding-sm; @font-size-base; @btn-border-radius-sm);
.button-size(@btn-height-sm; @btn-padding-sm; @btn-font-size-sm; @btn-border-radius-sm);
}
}
@ -251,8 +251,6 @@
.@{btnClassName}:not(:first-child):not(:last-child) {
border-radius: 0;
padding-left: 8px;
padding-right: 8px;
}
> .@{btnClassName}:first-child {
@ -260,14 +258,12 @@
&:not(:last-child) {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
padding-right: 8px;
}
}
> .@{btnClassName}:last-child:not(:first-child) {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
padding-left: 8px;
}
& > & {

View File

@ -1,5 +1,5 @@
import React from 'react';
import moment from 'moment';
import * as React from 'react';
import * as moment from 'moment';
import { PREFIX_CLS } from './Constants';
import Select from '../select';
import { Group, Button } from '../radio';
@ -12,7 +12,7 @@ export interface HeaderProps {
yearSelectOffset?: number;
yearSelectTotal?: number;
type?: string;
onValueChange?: (value) => void;
onValueChange?: (value: moment.Moment) => void;
onTypeChange?: (type: string) => void;
value: any;
}
@ -24,9 +24,9 @@ export default class Header extends React.Component<HeaderProps, any> {
yearSelectTotal: 20,
};
calenderHeaderNode: any;
private calenderHeaderNode: HTMLDivElement;
getYearSelectElement(year) {
getYearSelectElement(year: number) {
const { yearSelectOffset, yearSelectTotal, locale, prefixCls, fullscreen } = this.props;
const start = year - (yearSelectOffset as number);
const end = start + (yearSelectTotal as number);
@ -61,7 +61,7 @@ export default class Header extends React.Component<HeaderProps, any> {
return months;
}
getMonthSelectElement(month, months) {
getMonthSelectElement(month: number, months: number[]) {
const props = this.props;
const { prefixCls, fullscreen } = props;
const options: React.ReactElement<any>[] = [];
@ -84,7 +84,7 @@ export default class Header extends React.Component<HeaderProps, any> {
);
}
onYearChange = (year) => {
onYearChange = (year: string) => {
const newValue = this.props.value.clone();
newValue.year(parseInt(year, 10));
@ -94,7 +94,7 @@ export default class Header extends React.Component<HeaderProps, any> {
}
}
onMonthChange = (month) => {
onMonthChange = (month: string) => {
const newValue = this.props.value.clone();
newValue.month(parseInt(month, 10));
const onValueChange = this.props.onValueChange;
@ -103,14 +103,14 @@ export default class Header extends React.Component<HeaderProps, any> {
}
}
onTypeChange = (e) => {
onTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const onTypeChange = this.props.onTypeChange;
if (onTypeChange) {
onTypeChange(e.target.value);
}
}
getCalenderHeaderNode = (node) => {
getCalenderHeaderNode = (node: HTMLDivElement) => {
this.calenderHeaderNode = node;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
---
order: 2
title:
title:
zh-CN: 卡片模式
en-US: Card
---
@ -21,7 +21,7 @@ function onPanelChange(value, mode) {
}
ReactDOM.render(
<div style={{ width: 290, border: '1px solid #d9d9d9', borderRadius: 4 }}>
<div style={{ width: 300, border: '1px solid #d9d9d9', borderRadius: 4 }}>
<Calendar fullscreen={false} onPanelChange={onPanelChange} />
</div>
, mountNode);

View File

@ -14,7 +14,7 @@ title:
This component can be rendered by using `dateCellRender` and `monthCellRender` with the data you need.
````jsx
import { Calendar } from 'antd';
import { Calendar, Badge } from 'antd';
function getListData(value) {
let listData;
@ -22,18 +22,18 @@ function getListData(value) {
case 8:
listData = [
{ type: 'warning', content: 'This is warning event.' },
{ type: 'normal', content: 'This is usual event.' },
{ type: 'success', content: 'This is usual event.' },
]; break;
case 10:
listData = [
{ type: 'warning', content: 'This is warning event.' },
{ type: 'normal', content: 'This is usual event.' },
{ type: 'success', content: 'This is usual event.' },
{ type: 'error', content: 'This is error event.' },
]; break;
case 15:
listData = [
{ type: 'warning', content: 'This is warning event' },
{ type: 'normal', content: 'This is very long usual event。。....' },
{ type: 'success', content: 'This is very long usual event。。....' },
{ type: 'error', content: 'This is error event 1.' },
{ type: 'error', content: 'This is error event 2.' },
{ type: 'error', content: 'This is error event 3.' },
@ -51,8 +51,7 @@ function dateCellRender(value) {
{
listData.map(item => (
<li key={item.content}>
<span className={`event-${item.type}`}></span>
{item.content}
<Badge status={item.type} text={item.content} />
</li>
))
}
@ -83,42 +82,20 @@ ReactDOM.render(
````css
.events {
line-height: 24px;
list-style: none;
margin: 0;
padding: 0;
}
.events li {
color: #999;
.events .ant-badge-status {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
text-overflow: ellipsis;
font-size: 12px;
}
.events li span {
vertical-align: middle;
}
.events li span:first-child {
font-size: 9px;
margin-right: 4px;
}
.event-warning {
color: #fac450;
}
.event-normal {
color: #108ee9;
}
.event-error {
color: #f50;
}
.notes-month {
text-align: center;
font-size: 28px;
}
.notes-month section {
font-size: 28px;

View File

@ -16,11 +16,10 @@ When data is in the form of dates, such as schedules, timetables, prices calenda
**Note:** Part of the Calendar's locale is read from `value`. So, please set the locale of `moment` correctly.
```jsx
import moment from 'moment';
// It's recommended to set locale in entry file globaly.
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
// The default locale is en-US, if you want to use other locale, just set locale in entry file globaly.
// import moment from 'moment';
// import 'moment/locale/zh-cn';
// moment.locale('zh-cn');
<Calendar
dateCellRender={dateCellRender}

View File

@ -1,27 +1,25 @@
import React from 'react';
import * as React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import * as moment from 'moment';
import FullCalendar from 'rc-calendar/lib/FullCalendar';
import LocaleReceiver from '../locale-provider/LocaleReceiver';
import { PREFIX_CLS } from './Constants';
import Header from './Header';
import { getComponentLocale, getLocaleCode } from '../_util/getLocale';
import callMoment from '../_util/callMoment';
declare const require: Function;
export { HeaderProps } from './Header';
function noop() { return null; }
function zerofixed(v) {
function zerofixed(v: number) {
if (v < 10) {
return `0${v}`;
}
return `${v}`;
}
export interface CalendarContext {
antLocale?: {
Calendar?: any,
};
}
export type CalendarMode = 'month' | 'year';
export interface CalendarProps {
@ -43,7 +41,7 @@ export interface CalendarProps {
}
export interface CalendarState {
value?: moment.Moment;
value: moment.Moment;
mode?: CalendarMode;
}
@ -72,18 +70,10 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
onSelect: PropTypes.func,
};
static contextTypes = {
antLocale: PropTypes.object,
};
constructor(props: CalendarProps) {
super(props);
context: CalendarContext;
constructor(props, context) {
super(props, context);
// Make sure that moment locale had be set correctly.
getComponentLocale(props, context, 'Calendar', () => require('./locale/zh_CN'));
const value = props.value || props.defaultValue || moment();
const value = props.value || props.defaultValue || callMoment(moment);
if (!moment.isMoment(value)) {
throw new Error(
'The value/defaultValue of Calendar must be a moment object after `antd@2.0`, ' +
@ -99,12 +89,12 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
componentWillReceiveProps(nextProps: CalendarProps) {
if ('value' in nextProps) {
this.setState({
value: nextProps.value,
value: nextProps.value!,
});
}
}
monthCellRender = (value) => {
monthCellRender = (value: moment.Moment) => {
const { prefixCls, monthCellRender = noop as Function } = this.props;
return (
<div className={`${prefixCls}-month`}>
@ -118,7 +108,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
);
}
dateCellRender = (value) => {
dateCellRender = (value: moment.Moment) => {
const { prefixCls, dateCellRender = noop as Function } = this.props;
return (
<div className={`${prefixCls}-date`}>
@ -132,7 +122,12 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
);
}
setValue = (value, way: 'select' | 'changePanel') => {
getDefaultLocale() {
const locale = require('./locale/en_US');
return locale.default || locale;
}
setValue = (value: moment.Moment, way: 'select' | 'changePanel') => {
if (!('value' in this.props)) {
this.setState({ value });
}
@ -145,7 +140,7 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
}
}
setType = (type) => {
setType = (type: string) => {
const mode = (type === 'date') ? 'month' : 'year';
if (this.state.mode !== mode) {
this.setState({ mode });
@ -153,35 +148,33 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
}
}
onHeaderValueChange = (value) => {
onHeaderValueChange = (value: moment.Moment) => {
this.setValue(value, 'changePanel');
}
onHeaderTypeChange = (type) => {
onHeaderTypeChange = (type: string) => {
this.setType(type);
}
onPanelChange(value, mode) {
onPanelChange(value: moment.Moment, mode: CalendarMode | undefined) {
const { onPanelChange } = this.props;
if (onPanelChange) {
onPanelChange(value, mode);
}
}
onSelect = (value) => {
onSelect = (value: moment.Moment) => {
this.setValue(value, 'select');
}
render() {
const { state, props, context } = this;
renderCalendar = (locale: any, localeCode: string) => {
const { state, props } = this;
const { value, mode } = state;
const localeCode = getLocaleCode(context);
if (value && localeCode) {
value.locale(localeCode);
}
const { prefixCls, style, className, fullscreen, dateFullCellRender, monthFullCellRender } = props;
const type = (mode === 'year') ? 'month' : 'date';
const locale = getComponentLocale(props, context, 'Calendar', () => require('./locale/zh_CN'));
let cls = className || '';
if (fullscreen) {
@ -217,4 +210,15 @@ export default class Calendar extends React.Component<CalendarProps, CalendarSta
</div>
);
}
render() {
return (
<LocaleReceiver
componentName="Calendar"
defaultLocale={this.getDefaultLocale}
>
{this.renderCalendar}
</LocaleReceiver>
);
}
}

View File

@ -17,11 +17,10 @@ title: Calendar
**注意:**Calendar 部分 locale 是从 value 中读取,所以请先正确设置 moment 的 locale。
```jsx
import moment from 'moment';
// 推荐在入口文件全局设置 locale
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
// 默认语言为 en-US所以如果需要使用其他语言推荐在入口文件全局设置 locale
// import moment from 'moment';
// import 'moment/locale/zh-cn';
// moment.locale('zh-cn');
<Calendar
dateCellRender={dateCellRender}

View File

@ -0,0 +1,2 @@
import ar_EG from '../../date-picker/locale/ar_EG';
export default ar_EG;

View File

@ -0,0 +1,2 @@
import is_IS from '../../date-picker/locale/is_IS';
export default is_IS;

View File

@ -0,0 +1,2 @@
import uk_UA from '../../date-picker/locale/uk_UA';
export default uk_UA;

View File

@ -1,10 +1,10 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@full-calendar-prefix-cls: ~"@{ant-prefix}-fullcalendar";
.@{full-calendar-prefix-cls} {
font-size: @font-size-base;
line-height: @line-height-base;
.reset-component;
outline: none;
border-top: @border-width-base @border-style-base @border-color-base;
@ -38,7 +38,7 @@
}
&-calendar-body {
padding: 8px 8px 14px;
padding: 8px 12px;
}
table {
@ -46,7 +46,7 @@
max-width: 100%;
background-color: transparent;
width: 100%;
height: 246px;
height: 256px;
}
table,
@ -91,12 +91,12 @@
display: block;
margin: 0 auto;
color: @text-color;
border-radius: @border-radius-base;
width: 22px;
height: 22px;
border-radius: @border-radius-sm;
width: 24px;
height: 24px;
padding: 0;
background: transparent;
line-height: 22px;
line-height: 24px;
transition: all .3s;
&:hover {
@ -116,7 +116,7 @@
&-today &-value,
&-month-panel-current-cell &-value {
box-shadow: 0 0 0 1px @primary-color;
box-shadow: 0 0 0 1px @primary-color inset;
}
&-selected-day &-value,

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import classNames from 'classnames';
export interface CardGridProps {

33
components/card/Meta.tsx Normal file
View File

@ -0,0 +1,33 @@
import * as React from 'react';
import classNames from 'classnames';
export interface CardMetaProps {
prefixCls?: string;
style?: React.CSSProperties;
className?: string;
avatar?: React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
}
export default (props: CardMetaProps) => {
const { prefixCls = 'ant-card', className, avatar, title, description, ...others } = props;
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;
return (
<div {...others} className={classString}>
<div className={`${prefixCls}-meta-content`}>
{avatarDom}
{MetaDetail}
</div>
</div>
);
};

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ describe('Card', () => {
});
function fakeResizeWindowTo(wrapper, width) {
Object.defineProperties(wrapper.node.container, {
Object.defineProperties(wrapper.instance().container, {
offsetWidth: {
get() { return width; },
configurable: true,
@ -27,9 +27,11 @@ describe('Card', () => {
const wrapper = mount(<Card title="xxx">xxx</Card>);
fakeResizeWindowTo(wrapper, 1000);
jest.runAllTimers();
expect(wrapper.hasClass('ant-card-wider-padding')).toBe(true);
wrapper.update();
expect(wrapper.find('.ant-card-wider-padding').length).toBe(1);
fakeResizeWindowTo(wrapper, 800);
jest.runAllTimers();
expect(wrapper.hasClass('ant-card-wider-padding')).toBe(false);
wrapper.update();
expect(wrapper.find('.ant-card-wider-padding').length).toBe(0);
});
});

View File

@ -24,3 +24,9 @@ ReactDOM.render(
</Card>
, mountNode);
````
<style>
.code-box-demo p {
margin: 0;
}
</style>

View File

@ -0,0 +1,33 @@
---
order: 3
title:
zh-CN: 更灵活的内容展示
en-US: Customized content
---
## zh-CN
可以利用 `Card.Meta` 支持更灵活的内容。
## en-US
You can use `Card.Meta` to support more flexible content.
````jsx
import { Card } from 'antd';
const { Meta } = Card;
ReactDOM.render(
<Card
hoverable
style={{ width: 240 }}
cover={<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />}
>
<Meta
title="Europe Street beat"
description="www.instagram.com"
/>
</Card>
, mountNode);
````

View File

@ -22,7 +22,7 @@ const gridStyle = {
};
ReactDOM.render(
<Card title="Card Title" noHovering>
<Card title="Card Title">
<Card.Grid style={gridStyle}>Content</Card.Grid>
<Card.Grid style={gridStyle}>Content</Card.Grid>
<Card.Grid style={gridStyle}>Content</Card.Grid>

View File

@ -0,0 +1,48 @@
---
order: 7
title:
zh-CN: 内部卡片
en-US: Inner card
---
## zh-CN
可以放在普通卡片内部,展示多层级结构的信息。
## en-US
It can be placed inside the ordinary card to display the information of the multilevel structure.
````jsx
import { Card } from 'antd';
ReactDOM.render(
<Card title="Card title">
<p
style={{
fontSize: 14,
color: 'rgba(0, 0, 0, 0.85)',
marginBottom: 16,
fontWeight: 500,
}}
>
Group title
</p>
<Card
type="inner"
title="Inner Card title"
extra={<a href="#">More</a>}
>
Inner Card content
</Card>
<Card
style={{ marginTop: 16 }}
type="inner"
title="Inner Card title"
extra={<a href="#">More</a>}
>
Inner Card content
</Card>
</Card>
, mountNode);
````

View File

@ -0,0 +1,33 @@
---
order: 9
title:
zh-CN: 支持更多内容配置
en-US: Support more content configuration
---
## zh-CN
一种支持封面、头像、标题和描述信息的卡片。
## en-US
A Card that supports `cover`, `avatar`, `title` and `description`.
````jsx
import { Card, Icon, Avatar } from 'antd';
const { Meta } = Card;
ReactDOM.render(
<Card
style={{ width: 300 }}
cover={<img alt="example" src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png" />}
actions={[<Icon type="setting" />, <Icon type="edit" />, <Icon type="ellipsis" />]}
>
<Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title="Card title"
description="This is the description"
/>
</Card>
, mountNode);
````

View File

@ -1,43 +0,0 @@
---
order: 3
title:
zh-CN: 更灵活的内容展示
en-US: Customized content
---
## zh-CN
可以调整默认边距,设定宽度。
## en-US
Customizing default width and margin.
````jsx
import { Card } from 'antd';
ReactDOM.render(
<Card style={{ width: 240 }} bodyStyle={{ padding: 0 }}>
<div className="custom-image">
<img alt="example" width="100%" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />
</div>
<div className="custom-card">
<h3>Europe Street beat</h3>
<p>www.instagram.com</p>
</div>
</Card>
, mountNode);
````
````css
.custom-image img {
display: block;
}
.custom-card {
padding: 10px 16px;
}
.custom-card p {
color: #999;
}
````

View File

@ -0,0 +1,86 @@
---
order: 8
title:
zh-CN: 带页签的卡片
en-US: With tabs
---
## zh-CN
可承载更多内容。
## en-US
More content can be hosted.
````jsx
import { Card } from 'antd';
const tabList = [{
key: 'tab1',
tab: 'tab1',
}, {
key: 'tab2',
tab: 'tab2',
}];
const contentList = {
tab1: <p>content1</p>,
tab2: <p>content2</p>,
};
const tabListNoTitle = [{
key: 'article',
tab: 'article',
}, {
key: 'app',
tab: 'app',
}, {
key: 'project',
tab: 'project',
}];
const contentListNoTitle = {
article: <p>article content</p>,
app: <p>app content</p>,
project: <p>project content</p>,
};
class TabsCard extends React.Component {
state = {
key: 'tab1',
noTitleKey: 'article',
}
onTabChange = (key, type) => {
console.log(key, type);
this.setState({ [type]: key });
}
render() {
return (
<div>
<Card
style={{ width: '100%' }}
title="Card title"
extra={<a href="#">More</a>}
tabList={tabList}
onTabChange={(key) => { this.onTabChange(key, 'key'); }}
>
{contentList[this.state.key]}
</Card>
<br /><br />
<Card
style={{ width: '100%' }}
tabList={tabListNoTitle}
onTabChange={(key) => { this.onTabChange(key, 'noTitleKey'); }}
>
{contentListNoTitle[this.state.noTitleKey]}
</Card>
</div>
);
}
}
ReactDOM.render(
<TabsCard />
, mountNode);
````

View File

@ -21,12 +21,17 @@ A card can be used to display content related to a single subject. The content c
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| actions | The action list, shows at the bottom of the Card. | Array<ReactNode> | - |
| bodyStyle | Inline style to apply to the card content | object | - |
| bordered | Toggles rendering of the border around the card | boolean | `true` |
| cover | Card cover | ReactNode | - |
| extra | Content to render in the top-right corner of the card | string\|ReactNode | - |
| loading | Shows a loading indicator while the contents of the card are being fetched | boolean | `false` |
| noHovering | Whether to disable hover effect on mouse over | boolean | `false` |
| hoverable | Lift up when hovering card | boolean | false |
| loading | Shows a loading indicator while the contents of the card are being fetched | boolean | false |
| tabList | List of TabPane's head. | Array&lt;{key: string, tab: ReactNode}> | - |
| title | Card title | string\|ReactNode | - |
| type | Card style type, can be set to `inner` or not set | string | - |
| onTabChange | Callback when tab is switched | (key) => void | - |
### Card.Grid
@ -34,3 +39,13 @@ A card can be used to display content related to a single subject. The content c
| -------- | ----------- | ---- | ------- |
| className | className of container | string | - |
| style | style object of container | object | - |
### Card.Meta
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| avatar | avatar or icon | ReactNode | - |
| className | className of container | string | - |
| description | description content | ReactNode | - |
| style | style object of container | object | - |
| title | title content | ReactNode | - |

View File

@ -1,8 +1,22 @@
import React, { Component, Children } from 'react';
import * as React from 'react';
import classNames from 'classnames';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import omit from 'omit.js';
import Grid from './Grid';
import Meta from './Meta';
import Tabs from '../tabs';
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
import warning from '../_util/warning';
export { CardGridProps } from './Grid';
export { CardMetaProps } from './Meta';
export type CardType = 'inner';
export interface CardTabListType {
key: string;
tab: React.ReactNode;
}
export interface CardProps {
prefixCls?: string;
@ -13,22 +27,37 @@ export interface CardProps {
style?: React.CSSProperties;
loading?: boolean;
noHovering?: boolean;
hoverable?: boolean;
children?: React.ReactNode;
id?: string;
className?: string;
type?: CardType;
cover?: React.ReactNode;
actions?: Array<React.ReactNode>;
tabList?: CardTabListType[];
onTabChange?: (key: string) => void;
}
export default class Card extends Component<CardProps, {}> {
export default class Card extends React.Component<CardProps, {}> {
static Grid: typeof Grid = Grid;
container: HTMLDivElement;
static Meta: typeof Meta = Meta;
resizeEvent: any;
updateWiderPaddingCalled: boolean;
state = {
widerPadding: false,
};
private container: HTMLDivElement;
componentDidMount() {
this.updateWiderPadding();
this.resizeEvent = addEventListener(window, 'resize', this.updateWiderPadding);
if ('noHovering' in this.props) {
warning(
!this.props.noHovering,
'`noHovering` of Card is deperated, you can remove it safely or use `hoverable` instead.',
);
warning(!!this.props.noHovering, '`noHovering={false}` of Card is deperated, use `hoverable` instead.');
}
}
componentWillUnmount() {
if (this.resizeEvent) {
@ -54,73 +83,122 @@ export default class Card extends Component<CardProps, {}> {
});
}
}
onTabChange = (key: string) => {
if (this.props.onTabChange) {
this.props.onTabChange(key);
}
}
saveRef = (node: HTMLDivElement) => {
this.container = node;
}
isContainGrid() {
let containGrid;
Children.forEach(this.props.children, (element: JSX.Element) => {
React.Children.forEach(this.props.children, (element: JSX.Element) => {
if (element && element.type && element.type === Grid) {
containGrid = true;
}
});
return containGrid;
}
getAction(actions: React.ReactNode[]) {
if (!actions || !actions.length) {
return null;
}
const actionList = actions.map((action, index) => (
<li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
<span>{action}</span>
</li>
),
);
return actionList;
}
// For 2.x compatible
getCompatibleHoverable() {
const { noHovering, hoverable } = this.props;
if ('noHovering' in this.props) {
return !noHovering || hoverable;
}
return !!hoverable;
}
render() {
const {
prefixCls = 'ant-card', className, extra, bodyStyle, noHovering,
title, loading, bordered = true, ...others,
prefixCls = 'ant-card', className, extra, bodyStyle, noHovering, hoverable, title, loading,
bordered = true, type, cover, actions, tabList, children, ...others,
} = this.props;
let children = this.props.children;
const classString = classNames(prefixCls, className, {
[`${prefixCls}-loading`]: loading,
[`${prefixCls}-bordered`]: bordered,
[`${prefixCls}-no-hovering`]: noHovering,
[`${prefixCls}-hoverable`]: this.getCompatibleHoverable(),
[`${prefixCls}-wider-padding`]: this.state.widerPadding,
[`${prefixCls}-padding-transition`]: this.updateWiderPaddingCalled,
[`${prefixCls}-contain-grid`]: this.isContainGrid(),
[`${prefixCls}-contain-tabs`]: tabList && tabList.length,
[`${prefixCls}-type-${type}`]: !!type,
});
if (loading) {
children = (
<div className={`${prefixCls}-loading-content`}>
<p className={`${prefixCls}-loading-block`} style={{ width: '94%' }} />
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '28%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '62%' }} />
</p>
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '22%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '66%' }} />
</p>
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '56%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '39%' }} />
</p>
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '21%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '15%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '40%' }} />
</p>
</div>
);
}
const loadingBlock = (
<div className={`${prefixCls}-loading-content`}>
<p className={`${prefixCls}-loading-block`} style={{ width: '94%' }} />
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '28%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '62%' }} />
</p>
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '22%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '66%' }} />
</p>
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '56%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '39%' }} />
</p>
<p>
<span className={`${prefixCls}-loading-block`} style={{ width: '21%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '15%' }} />
<span className={`${prefixCls}-loading-block`} style={{ width: '40%' }} />
</p>
</div>
);
let head;
if (title || extra) {
const tabs = tabList && tabList.length ? (
<Tabs className={`${prefixCls}-head-tabs`} size="large" onChange={this.onTabChange}>
{tabList.map(item => <Tabs.TabPane tab={item.tab} key={item.key} />)}
</Tabs>
) : null;
if (title || extra || tabs) {
head = (
<div className={`${prefixCls}-head`}>
{title ? <div className={`${prefixCls}-head-title`}>{title}</div> : null}
{extra ? <div className={`${prefixCls}-extra`}>{extra}</div> : null}
<div className={`${prefixCls}-head-wrapper`}>
{title && <div className={`${prefixCls}-head-title`}>{title}</div>}
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
</div>
{tabs}
</div>
);
}
return (
<div {...others} className={classString} ref={this.saveRef}>
const coverDom = cover ? <div className={`${prefixCls}-cover`}>{cover}</div> : null;
const body = (
<div className={`${prefixCls}-body`} style={bodyStyle}>
{loading ? loadingBlock : <div>{children}</div>}
</div>
);
const mainContent = (
<div>
{head}
<div className={`${prefixCls}-body`} style={bodyStyle}>{children}</div>
{coverDom}
{children ? body : null}
</div>
);
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}>
{mainContent}
{actionDom}
</div>
);
}

View File

@ -22,12 +22,17 @@ cols: 1
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| actions | 卡片操作组,位置在卡片底部 | Array<ReactNode> | - |
| bodyStyle | 内容区域自定义样式 | object | - |
| bordered | 是否有边框 | boolean | true |
| cover | 卡片封面 | ReactNode | - |
| extra | 卡片右上角的操作区域 | string\|ReactNode | - |
| hoverable | 鼠标移过时可浮起 | boolean | false |
| loading | 当卡片内容还在加载中时,可以用 loading 展示一个占位 | boolean | false |
| noHovering | 取消鼠标移过浮起 | boolean | false |
| tabList | 页签标题列表 | Array&lt;{key: string, tab: ReactNode}> | - |
| title | 卡片标题 | string\|ReactNode | - |
| type | 卡片类型,可设置为 `inner` 或 不设置 | string | - |
| onTabChange | 页签切换的回调 | (key) => void | - |
### Card.Grid
@ -35,3 +40,13 @@ cols: 1
| -------- | ----------- | ---- | ------- |
| className | 网格容器类名 | string | - |
| style | 定义网格容器类名的样式 | object | - |
### Card.Meta
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| avatar | 头像/图标 | ReactNode | - |
| className | 容器类名 | string | - |
| description | 描述内容 | ReactNode | - |
| style | 定义容器类名的样式 | object | - |
| title | 标题内容 | ReactNode | - |

View File

@ -2,19 +2,21 @@
@import "../../style/mixins/index";
@card-prefix-cls: ~"@{ant-prefix}-card";
@card-padding-base: 24px;
@card-padding-wider: 32px;
@card-head-height: 48px;
.@{card-prefix-cls} {
.reset-component;
background: @component-background;
border-radius: @border-radius-sm;
font-size: @font-size-base;
position: relative;
transition: all .3s;
&:not(&-no-hovering):hover {
box-shadow: @box-shadow-base;
border-color: @shadow-color;
&-hoverable {
cursor: pointer;
&:hover {
box-shadow: @card-shadow;
border-color: rgba(0, 0, 0, 0.09);
}
}
&-bordered {
@ -22,20 +24,22 @@
}
&-head {
height: @card-head-height;
line-height: @card-head-height;
background: @card-head-background;
border-bottom: @border-width-base @border-style-base @border-color-split;
padding: 0 @card-padding-base;
border-radius: @border-radius-sm @border-radius-sm 0 0;
.clearfix;
margin-bottom: -1px; // Fix card grid overflow bug: https://gw.alipayobjects.com/zos/rmsportal/XonYxBikwpgbqIQBeuhk.png
display: flex;
min-height: @card-head-height;
&-wrapper {
display: flex;
}
&-title {
font-size: @font-size-lg;
padding: @card-head-padding 0;
text-overflow: ellipsis;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
color: @card-head-color;
@ -43,10 +47,20 @@
display: inline-block;
flex: 1;
}
.@{ant-prefix}-tabs {
margin-bottom: -17px;
clear: both;
&-bar {
border-bottom: @border-width-base @border-style-base @border-color-split;
}
}
}
&-extra {
float: right;
padding: @card-head-padding + 1.5px 0;
text-align: right;
// https://stackoverflow.com/a/22429853/3040605
margin-left: auto;
@ -77,6 +91,66 @@
}
}
&-contain-tabs &-head-title {
padding-bottom: 0;
min-height: @card-head-height - @card-head-padding;
}
&-contain-tabs &-extra {
padding-bottom: 0;
}
&-cover > * {
width: 100%;
display: block;
}
&-actions {
border-top: @border-width-base @border-style-base @border-color-split;
background: @card-actions-background;
.clearfix;
list-style: none;
margin: 0;
padding: 0;
& > li {
float: left;
text-align: center;
margin: 12px 0;
color: @text-color-secondary;
& > span {
display: inline-block;
font-size: 14px;
cursor: pointer;
line-height: 22px;
min-width: 32px;
position: relative;
&:hover {
color: @primary-color;
transition: color .3s;
}
& > .anticon {
font-size: 16px;
}
a {
color: @text-color-secondary;
&:hover {
color: @primary-color;
}
}
}
&:not(:last-child) {
border-right: @border-width-base @border-style-base @border-color-split;
}
}
}
&-wider-padding &-head {
padding: 0 @card-padding-wider;
}
@ -85,10 +159,6 @@
padding: @card-padding-base @card-padding-wider;
}
&-wider-padding &-extra {
right: @card-padding-wider;
}
&-padding-transition &-head,
&-padding-transition &-body {
transition: padding .3s;
@ -98,6 +168,61 @@
transition: right .3s;
}
&-type-inner &-head {
padding: 0 @card-padding-base;
background: @background-color-light;
&-title {
padding: @card-inner-head-padding 0;
font-size: @font-size-base;
}
}
&-type-inner &-body {
padding: 16px @card-padding-base;
}
&-type-inner &-extra {
padding: @card-inner-head-padding + 1.5px 0;
}
&-meta {
margin: -4px 0;
&-content {
display: table-row;
}
&-avatar {
padding-right: 16px;
display: table-cell;
}
&-detail {
display: table-cell;
vertical-align: top;
position: relative;
& > div:not(:last-child) {
margin-bottom: 8px;
}
}
&-title {
font-size: @font-size-lg;
text-overflow: ellipsis;
width: 100%;
overflow: hidden;
white-space: nowrap;
color: @card-head-color;
font-weight: 500;
}
&-description {
color: @text-color-secondary;
}
}
&-loading &-body {
user-select: none;
padding: 0;
@ -105,11 +230,14 @@
&-loading-content {
padding: @card-padding-base;
p {
margin: 0;
}
}
&-loading-block {
display: inline-block;
margin: 5px 1% 0;
margin: 5px 2% 0 0;
height: 14px;
border-radius: @border-radius-sm;
background: linear-gradient(90deg, rgba(207, 216, 220, .2), rgba(207, 216, 220, .4), rgba(207, 216, 220, .2));

View File

@ -1,2 +1,5 @@
import '../../style/index.less';
import './index.less';
// style dependencies
import '../../tabs/style';

View File

@ -24,7 +24,7 @@ exports[`renders ./components/carousel/demo/autoplay.md correctly 1`] = `
<div
class="slick-slide slick-active slick-cloned"
data-index="0"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -34,7 +34,7 @@ exports[`renders ./components/carousel/demo/autoplay.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="1"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -44,7 +44,7 @@ exports[`renders ./components/carousel/demo/autoplay.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="2"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -54,7 +54,7 @@ exports[`renders ./components/carousel/demo/autoplay.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="3"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -99,7 +99,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
<div
class="slick-slide slick-active slick-cloned"
data-index="0"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -109,7 +109,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="1"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -119,7 +119,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="2"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -129,7 +129,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="3"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -166,7 +166,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
<div
class="slick-slide slick-active slick-cloned"
data-index="0"
style="outline:none;position:relative;left:0;opacity:1;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease;"
style="outline:none;position:relative;left:0;opacity:1;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease"
tabindex="-1"
>
<h3>
@ -176,7 +176,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="1"
style="outline:none;position:relative;left:0;opacity:0;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease;"
style="outline:none;position:relative;left:0;opacity:0;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease"
tabindex="-1"
>
<h3>
@ -186,7 +186,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="2"
style="outline:none;position:relative;left:0;opacity:0;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease;"
style="outline:none;position:relative;left:0;opacity:0;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease"
tabindex="-1"
>
<h3>
@ -196,7 +196,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="3"
style="outline:none;position:relative;left:0;opacity:0;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease;"
style="outline:none;position:relative;left:0;opacity:0;transition:opacity 500ms ease;-webkit-transition:opacity 500ms ease"
tabindex="-1"
>
<h3>
@ -233,7 +233,7 @@ exports[`renders ./components/carousel/demo/vertical.md correctly 1`] = `
<div
class="slick-slide slick-active slick-cloned"
data-index="0"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -243,7 +243,7 @@ exports[`renders ./components/carousel/demo/vertical.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="1"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -253,7 +253,7 @@ exports[`renders ./components/carousel/demo/vertical.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="2"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>
@ -263,7 +263,7 @@ exports[`renders ./components/carousel/demo/vertical.md correctly 1`] = `
<div
class="slick-slide slick-cloned"
data-index="3"
style="outline:none;"
style="outline:none"
tabindex="-1"
>
<h3>

View File

@ -5,8 +5,8 @@ import Carousel from '..';
describe('Carousel', () => {
it('should has innerSlider', () => {
const wrapper = mount(<Carousel><div /></Carousel>);
const { innerSlider } = wrapper.node;
const innerSliderFromRefs = wrapper.node.refs.slick.innerSlider;
const { innerSlider } = wrapper.instance();
const innerSliderFromRefs = wrapper.instance().slick.innerSlider;
expect(innerSlider).toBe(innerSliderFromRefs);
expect(typeof innerSlider.slickNext).toBe('function');
});

View File

@ -24,4 +24,12 @@ A carousel component. Scales with its container.
| effect | Transition effect | `scrollx` \| `fade` | `scrollx` |
| vertical | Whether to use a vertical display | boolean | `false` |
## Methods
| Name | Description |
| ---- | ----------- |
| goTo(slideNumber) | Change current slide to given slide number |
| next() | Change current slide to next slide |
| prev() | Change current slide to previous slide |
For more info on the parameters, refer to the <https://github.com/akiran/react-slick>

View File

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import debounce from 'lodash.debounce';
// matchMedia polyfill for
@ -66,7 +66,7 @@ export interface CarouselProps {
slickGoTo?: number;
}
export default class Carousel extends React.Component<CarouselProps, any> {
export default class Carousel extends React.Component<CarouselProps, {}> {
static defaultProps = {
dots: true,
arrows: false,
@ -74,13 +74,11 @@ export default class Carousel extends React.Component<CarouselProps, any> {
draggable: false,
};
refs: {
slick: any,
};
innerSlider: any;
constructor(props) {
private slick: any;
constructor(props: CarouselProps) {
super(props);
this.onWindowResized = debounce(this.onWindowResized, 500, {
leading: false,
@ -92,9 +90,8 @@ export default class Carousel extends React.Component<CarouselProps, any> {
if (autoplay) {
window.addEventListener('resize', this.onWindowResized);
}
const { slick } = this.refs;
// https://github.com/ant-design/ant-design/issues/7191
this.innerSlider = slick && slick.innerSlider;
this.innerSlider = this.slick && this.slick.innerSlider;
}
componentWillUnmount() {
@ -107,13 +104,28 @@ export default class Carousel extends React.Component<CarouselProps, any> {
onWindowResized = () => {
// Fix https://github.com/ant-design/ant-design/issues/2550
const { slick } = this.refs;
const { autoplay } = this.props;
if (autoplay && slick && slick.innerSlider && slick.innerSlider.autoPlay) {
slick.innerSlider.autoPlay();
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();
}
prev() {
this.slick.slickPrev();
}
goTo(slide: number) {
this.slick.slickGoTo(slide);
}
render() {
let props = {
...this.props,
@ -130,7 +142,7 @@ export default class Carousel extends React.Component<CarouselProps, any> {
return (
<div className={className}>
<SlickCarousel ref="slick" {...props} />
<SlickCarousel ref={this.saveSlick} {...props} />
</div>
);
}

View File

@ -25,4 +25,12 @@ subtitle: 走马灯
| effect | 动画效果函数,可取 scrollx, fade | string | scrollx |
| vertical | 垂直显示 | boolean | false |
## 方法
| 名称 | 描述 |
| --- | --- |
| goTo(slideNumber) | 切换到指定面板 |
| next() | 切换到下一面板 |
| prev() | 切换到上一面板 |
更多参数可参考:<https://github.com/akiran/react-slick>

View File

@ -2,6 +2,8 @@
@import "../../style/mixins/index";
.@{ant-prefix}-carousel {
.reset-component;
.slick-slider {
position: relative;
display: block;
@ -141,6 +143,7 @@
list-style: none;
display: block;
text-align: center;
margin: 0;
padding: 0;
width: 100%;
height: @carousel-dot-height;
@ -164,6 +167,7 @@
font-size: 0;
color: transparent;
transition: all .5s;
padding: 0;
&:hover,
&:focus {
opacity: 0.75;

View File

@ -47,7 +47,7 @@ exports[`renders ./components/cascader/demo/change-on-select.md correctly 1`] =
exports[`renders ./components/cascader/demo/custom-render.md correctly 1`] = `
<span
class="ant-cascader-picker"
style="width:270px;"
style="width:100%"
tabindex="0"
>
<span
@ -211,7 +211,7 @@ exports[`renders ./components/cascader/demo/search.md correctly 1`] = `
exports[`renders ./components/cascader/demo/size.md correctly 1`] = `
<div>
<span
class="ant-cascader-picker"
class="ant-cascader-picker ant-cascader-picker-large"
tabindex="0"
>
<span
@ -253,7 +253,7 @@ exports[`renders ./components/cascader/demo/size.md correctly 1`] = `
<br />
<br />
<span
class="ant-cascader-picker"
class="ant-cascader-picker ant-cascader-picker-small"
tabindex="0"
>
<span

View File

@ -2,6 +2,7 @@ import React from 'react';
import { render, mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import Cascader from '..';
import focusTest from '../../../tests/shared/focusTest';
const options = [{
value: 'zhejiang',
@ -28,11 +29,13 @@ const options = [{
}];
describe('Cascader', () => {
focusTest(Cascader);
it('popup correctly when panel is hidden', () => {
const wrapper = mount(
<Cascader options={options} />
);
expect(render(wrapper.find('Trigger').node.getComponent())).toMatchSnapshot();
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
});
it('popup correctly when panel is open', () => {
@ -40,7 +43,7 @@ describe('Cascader', () => {
<Cascader options={options} />
);
wrapper.find('input').simulate('click');
expect(render(wrapper.find('Trigger').node.getComponent())).toMatchSnapshot();
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
});
it('popup correctly with defaultValue', () => {
@ -48,24 +51,24 @@ describe('Cascader', () => {
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />
);
wrapper.find('input').simulate('click');
expect(render(wrapper.find('Trigger').node.getComponent())).toMatchSnapshot();
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
});
it('can be selected', () => {
const wrapper = mount(<Cascader options={options} />);
wrapper.find('input').simulate('click');
let popupWrapper = mount(wrapper.find('Trigger').node.getComponent());
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').node.getComponent())).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Trigger').node.getComponent());
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').node.getComponent())).toMatchSnapshot();
popupWrapper = mount(wrapper.find('Trigger').node.getComponent());
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').node.getComponent())).toMatchSnapshot();
expect(render(wrapper.find('Trigger').instance().getComponent())).toMatchSnapshot();
});
it('backspace should work with `Cascader[showSearch]`', () => {

View File

@ -64,7 +64,7 @@ ReactDOM.render(
options={options}
defaultValue={['zhejiang', 'hangzhou', 'xihu']}
displayRender={displayRender}
style={{ width: 270 }}
style={{ width: '100%' }}
/>
, mountNode);
````

View File

@ -23,7 +23,7 @@ Cascade selection box.
| allowClear | whether allow clear | boolean | true |
| changeOnSelect | change value on each selection if set to true, see above demo for details | boolean | false |
| className | additional css class | string | - |
| defaultValue | initial selected value | [CascaderOptionType](https://git.io/vMMoK)\[] | \[] |
| defaultValue | initial selected value | string\[] | \[] |
| disabled | whether disabled select | boolean | false |
| displayRender | render function of displaying selected options | `(label, selectedOptions) => ReactNode` | `label => label.join(' / ')` |
| expandTrigger | expand current item when click or hover, one of 'click' 'hover' | string | 'click' |
@ -37,7 +37,7 @@ Cascade selection box.
| showSearch | Whether show search input in single mode. | boolean\|object | false |
| size | input size, one of `large` `default` `small` | string | `default` |
| style | additional style | string | - |
| value | selected value | [CascaderOptionType](https://git.io/vMMoK)\[] | - |
| value | selected value | string\[] | - |
| onChange | callback when finishing cascader select | `(value, selectedOptions) => void` | - |
| onPopupVisibleChange | callback when popup shown or hidden | `(value) => void` | - |
| popupVisible | set visible of cascader popup | boolean | - |
@ -53,6 +53,6 @@ Fields in `showSearch`:
<style>
.ant-cascader-picker {
width: 220px;
width: 300px;
}
</style>

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