diff --git a/components/__tests__/__snapshots__/index.test.js.snap b/components/__tests__/__snapshots__/index.test.js.snap index 53f98628bc..072e985c04 100644 --- a/components/__tests__/__snapshots__/index.test.js.snap +++ b/components/__tests__/__snapshots__/index.test.js.snap @@ -41,6 +41,7 @@ Array [ "Rate", "Row", "Select", + "Skeleton", "Slider", "Spin", "Steps", diff --git a/components/_util/wave.tsx b/components/_util/wave.tsx index 3642555fd5..7a00f4598f 100644 --- a/components/_util/wave.tsx +++ b/components/_util/wave.tsx @@ -7,6 +7,8 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> { cancel: () => void; }; + private extraNode: HTMLDivElement; + private clickWaveTimeoutId: number; private styleForPesudo: HTMLStyleElement | null; isNotGrey(color: string) { @@ -17,22 +19,17 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> { return true; } - onClick = (node: HTMLElement) => { + onClick = (node: HTMLElement, waveColor: string) => { if (node.className.indexOf('-leave') >= 0) { return; } - this.removeExtraStyleNode(); const { insertExtraNode } = this.props; - const extraNode = document.createElement('div'); + this.extraNode = document.createElement('div'); + const extraNode = this.extraNode; extraNode.className = 'ant-click-animating-node'; - const attributeName = insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node'; + const attributeName = this.getAttributeName(); node.removeAttribute(attributeName); node.setAttribute(attributeName, 'true'); - // Get wave color from target - const waveColor = - getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible - getComputedStyle(node).getPropertyValue('border-color') || - getComputedStyle(node).getPropertyValue('background-color'); // Not white or transparnt or grey if (waveColor && waveColor !== '#ffffff' && @@ -49,19 +46,13 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> { if (insertExtraNode) { node.appendChild(extraNode); } - const transitionEnd = () => { - node.removeAttribute(attributeName); - this.removeExtraStyleNode(); - if (insertExtraNode) { - node.removeChild(extraNode); - } - TransitionEvents.removeEndEventListener(node, transitionEnd); - }; - TransitionEvents.addEndEventListener(node, transitionEnd); + TransitionEvents.addEndEventListener(node, this.onTransitionEnd); } bindAnimationEvent = (node: HTMLElement) => { - if (node.getAttribute('disabled') || + if (!node || + !node.getAttribute || + node.getAttribute('disabled') || node.className.indexOf('disabled') >= 0) { return; } @@ -70,7 +61,13 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> { if ((e.target as HTMLElement).tagName === 'INPUT') { return; } - setTimeout(() => this.onClick(node), 0); + this.resetEffect(node); + // Get wave color from target + const waveColor = + getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible + getComputedStyle(node).getPropertyValue('border-color') || + getComputedStyle(node).getPropertyValue('background-color'); + this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0); }; node.addEventListener('click', onClick, true); return { @@ -80,6 +77,29 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> { }; } + getAttributeName() { + const { insertExtraNode } = this.props; + return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node'; + } + + resetEffect(node: HTMLElement) { + const { insertExtraNode } = this.props; + const attributeName = this.getAttributeName(); + node.removeAttribute(attributeName); + this.removeExtraStyleNode(); + if (insertExtraNode) { + node.removeChild(this.extraNode); + } + TransitionEvents.removeEndEventListener(node, this.onTransitionEnd); + } + + onTransitionEnd = (e: AnimationEvent) => { + if (!e || e.animationName !== 'fadeEffect') { + return; + } + this.resetEffect(e.target as HTMLElement); + } + removeExtraStyleNode() { if (this.styleForPesudo && document.body.contains(this.styleForPesudo)) { document.body.removeChild(this.styleForPesudo); @@ -95,6 +115,9 @@ export default class Wave extends React.Component<{insertExtraNode?: boolean}> { if (this.instance) { this.instance.cancel(); } + if (this.clickWaveTimeoutId) { + clearTimeout(this.clickWaveTimeoutId); + } } render() { diff --git a/components/button/__tests__/index.test.js b/components/button/__tests__/index.test.js index d6e2542294..38930a6102 100644 --- a/components/button/__tests__/index.test.js +++ b/components/button/__tests__/index.test.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import { render, mount } from 'enzyme'; +import renderer from 'react-test-renderer'; import Button from '..'; import Icon from '../../icon'; @@ -11,6 +12,16 @@ describe('Button', () => { expect(wrapper).toMatchSnapshot(); }); + it('mount correctly', () => { + const wrapper = mount( + + ); + if (process.env.REACT === '15') { + return; + } + expect(() => renderer.create(wrapper).toJSON()).not.toThrow(); + }); + it('renders Chinese characters correctly', () => { const wrapper = render( diff --git a/components/button/demo/ghost.md b/components/button/demo/ghost.md index 9cff4953e9..7c7f77a522 100644 --- a/components/button/demo/ghost.md +++ b/components/button/demo/ghost.md @@ -7,7 +7,7 @@ title: ## zh-CN -幽灵按钮将其他按钮的内容反色,背景变为透明,常用在有色背景上。 +幽灵按钮将按钮的内容反色,背景变为透明,常用在有色背景上。 ## en-US diff --git a/components/card/__tests__/__snapshots__/demo.test.js.snap b/components/card/__tests__/__snapshots__/demo.test.js.snap index 02030679ec..74c318e496 100644 --- a/components/card/__tests__/__snapshots__/demo.test.js.snap +++ b/components/card/__tests__/__snapshots__/demo.test.js.snap @@ -372,174 +372,83 @@ exports[`renders ./components/card/demo/inner.md correctly 1`] = ` exports[`renders ./components/card/demo/loading.md correctly 1`] = `
-
+ + +
-
-
-
- Card title -
-
-
-
-
-
+
-
+
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
+
    +
  • + + + +
  • +
  • + + + +
  • +
  • + + + +
  • +
-
`; diff --git a/components/card/demo/loading.md b/components/card/demo/loading.md index 408d44d240..c3e39deb0a 100644 --- a/components/card/demo/loading.md +++ b/components/card/demo/loading.md @@ -14,30 +14,42 @@ title: Shows a loading indicator while the contents of the card is being fetched. ````jsx -import { Card, Button } from 'antd'; +import { Skeleton, Switch, Card, Icon, Avatar } from 'antd'; -class LoadingCard extends React.Component { +const { Meta } = Card; + +class App extends React.Component { state = { loading: true, } - handleClick = () => { - this.setState({ loading: !this.state.loading }); + onChange = (checked) => { + this.setState({ loading: !checked }); } render() { + const { loading } = this.state; + return (
- - Whatever content + + + , , ]} + > + + } + title="Card title" + description="This is the description" + /> + -
); } } -ReactDOM.render( - , - mountNode); +ReactDOM.render(, mountNode); ```` diff --git a/components/date-picker/WeekPicker.tsx b/components/date-picker/WeekPicker.tsx index 3a6ee610b5..9c71faacb9 100644 --- a/components/date-picker/WeekPicker.tsx +++ b/components/date-picker/WeekPicker.tsx @@ -87,7 +87,7 @@ class WeekPicker extends React.Component { const { prefixCls, className, disabled, pickerClass, popupStyle, pickerInputClass, format, allowClear, locale, localeCode, disabledDate, - style, onFocus, onBlur, + style, onFocus, onBlur, id, } = this.props; const pickerValue = this.state.value; @@ -129,7 +129,6 @@ class WeekPicker extends React.Component { className={pickerInputClass} onFocus={onFocus} onBlur={onBlur} - style={style} /> {clearIcon} @@ -137,7 +136,11 @@ class WeekPicker extends React.Component { ); }; return ( - + { expect(wrapper).toMatchSnapshot(); }); + it('render top drawer', () => { + const wrapper = render( + + Here is content of Drawer + + ); + expect(wrapper).toMatchSnapshot(); + }); + it('have a title', () => { const wrapper = render( { expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }); - it('render right MultiDrawer', () => { + it('render left MultiDrawer', () => { const wrapper = mount(); wrapper.find('button#open_drawer').simulate('click'); wrapper.find('button#open_two_drawer').simulate('click'); const translateX = wrapper.find('.ant-drawer.test_drawer').get(0).props.style.transform; expect(translateX).toEqual('translateX(180px)'); expect(wrapper.find('#two_drawer_text').exists()).toBe(true); + wrapper.find('.Two-level .ant-drawer-close').simulate('click'); + expect(wrapper.state().childrenDrawer).toBe(false); + }); + + it('render left MultiDrawer', () => { + const wrapper = mount(); + wrapper.find('button#open_drawer').simulate('click'); + wrapper.find('button#open_two_drawer').simulate('click'); + const translateX = wrapper.find('.ant-drawer.test_drawer').get(0).props.style.transform; + expect(translateX).toEqual('translateY(180px)'); + expect(wrapper.find('#two_drawer_text').exists()).toBe(true); }); }); diff --git a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap index 6c8325225d..6ad726e1bb 100644 --- a/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap +++ b/components/drawer/__tests__/__snapshots__/Drawer.test.js.snap @@ -277,3 +277,43 @@ exports[`Drawer render correctly 1`] = `
`; + +exports[`Drawer render top drawer 1`] = ` +
+
+
+
+
+
+ +
+ Here is content of Drawer +
+
+
+
+
+
+`; diff --git a/components/drawer/demo/basic-left.md b/components/drawer/demo/basic-left.md deleted file mode 100644 index 1caf745dea..0000000000 --- a/components/drawer/demo/basic-left.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -order: 1 -title: - zh-CN: 左侧滑出 - en-US: Left Slider ---- - -## zh-CN - -基础抽屉,点击触发按钮抽屉从左滑出,点击遮罩区关闭 - -## en-US - -Basic drawer. - -```jsx -import { Drawer, Button } from 'antd'; - -class App extends React.Component { - state = { visible: false }; - - showDrawer = () => { - this.setState({ - visible: true, - }); - }; - - onClose = () => { - this.setState({ - visible: false, - }); - }; - - render() { - return ( -
- - -

Some contents...

-

Some contents...

-

Some contents...

-
-
- ); - } -} - -ReactDOM.render(, mountNode); -``` diff --git a/components/drawer/demo/placement.md b/components/drawer/demo/placement.md new file mode 100644 index 0000000000..a1a5907a0b --- /dev/null +++ b/components/drawer/demo/placement.md @@ -0,0 +1,75 @@ +--- +order: 1 +title: + zh-CN: 自定义位置 + en-US: Custom Placement +--- + +## zh-CN + +自定义位置,点击触发按钮抽屉从相应的位置滑出,点击遮罩区关闭 + +## en-US + +Basic drawer. + +```jsx +import { Drawer, Button, Radio } from 'antd'; + +const RadioGroup = Radio.Group; + +class App extends React.Component { + state = { visible: false, placement: 'left' }; + + showDrawer = () => { + this.setState({ + visible: true, + }); + }; + + onClose = () => { + this.setState({ + visible: false, + }); + }; + + onChange = (e) => { + this.setState({ + placement: e.target.value, + }); + } + + render() { + return ( +
+ + top + right + bottom + left + + + +

Some contents...

+

Some contents...

+

Some contents...

+
+
+ ); + } +} + +ReactDOM.render(, mountNode); +``` diff --git a/components/drawer/index.en-US.md b/components/drawer/index.en-US.md index 2b7bfc60b5..eb9b33bac4 100644 --- a/components/drawer/index.en-US.md +++ b/components/drawer/index.en-US.md @@ -27,9 +27,10 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr | title | The title for Drawer. | string\|ReactNode | - | | visible | Whether the Drawer dialog is visible or not. | boolean | false | | width | Width of the Drawer dialog. | string\|number | 256 | +| height | placement is `top` or `bottom`, height of the Drawer dialog. | string\|number | - | | className | The class name of the container of the Drawer dialog. | string | - | | zIndex | The `z-index` of the Drawer. | Number | 1000 | -| placement | The placement of the Drawer. | 'left' \| 'right' | 'right' +| placement | The placement of the Drawer. | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - | diff --git a/components/skeleton/demo/list.md b/components/skeleton/demo/list.md new file mode 100644 index 0000000000..5c14a55554 --- /dev/null +++ b/components/skeleton/demo/list.md @@ -0,0 +1,86 @@ +--- +order: 3 +title: + zh-CN: 列表 + en-US: List +--- + +## zh-CN + +在列表组件中使用加载占位符。 + +## en-US + +Use skeleton in list component. + +````jsx +import { Skeleton, Switch, List, Avatar, Icon } from 'antd'; + +const listData = []; +for (let i = 0; i < 3; i++) { + listData.push({ + href: 'http://ant.design', + title: `ant design part ${i}`, + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png', + description: 'Ant Design, a design language for background applications, is refined by Ant UED Team.', + content: 'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.', + }); +} + +const IconText = ({ type, text }) => ( + + + {text} + +); + +class App extends React.Component { + state = { + loading: true, + } + + onChange = (checked) => { + this.setState({ loading: !checked }); + } + + render() { + const { loading } = this.state; + + return ( +
+ + + ( + , , ]} + extra={!loading && logo} + > + + } + title={{item.title}} + description={item.description} + /> + {item.content} + + + )} + /> +
+ ); + } +} + +ReactDOM.render(, mountNode); +```` + + diff --git a/components/skeleton/index.en-US.md b/components/skeleton/index.en-US.md new file mode 100644 index 0000000000..607db7d533 --- /dev/null +++ b/components/skeleton/index.en-US.md @@ -0,0 +1,44 @@ +--- +category: Components +type: Data Entry +title: Skeleton +cols: 1 +--- + +Provide a placeholder at the place which need waiting for loading. +## When To Use + +- When resource needs long time to load, like low network speed. +- The component contains much information. Such as List or Card. + +## API + +### Skeleton + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| active | Show animation effect | boolean | false | +| avatar | Show avatar placeholder | boolean \| [SkeletonAvatarProps](#SkeletonAvatarProps) | false | +| loading | Display the skeleton when `true` | boolean | - | +| paragraph | Show paragraph placeholder | boolean \| [SkeletonParagraphProps](#SkeletonParagraphProps) | true | +| title | Show title placeholder | boolean \| [SkeletonTitleProps](#SkeletonTitleProps) | true | + +### SkeletonAvatarProps + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| size | Set the size of avatar | Enum{ 'large', 'small', 'default' } | - | +| shape | Set the shape of avatar | Enum{ 'circle', 'square' } | - | + +### SkeletonTitleProps + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| width | Set the width of title | number \| string | - | + +### SkeletonParagraphProps + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| rows | Set the row count of paragraph | number | - | +| width | Set the width of paragraph. When width is an Array, it can set the width of each row. Otherwise only set the last row width | number \| string \| Array | - | diff --git a/components/skeleton/index.tsx b/components/skeleton/index.tsx new file mode 100644 index 0000000000..bf2064b5f4 --- /dev/null +++ b/components/skeleton/index.tsx @@ -0,0 +1,155 @@ +import * as React from 'react'; +import classNames from 'classnames'; +import Avatar, { SkeletonAvatarProps } from './Avatar'; +import Title, { SkeletonTitleProps } from './Title'; +import Paragraph, { SkeletonParagraphProps } from './Paragraph'; + +export interface SkeletonProps { + active?: boolean; + loading?: boolean; + prefixCls?: string; + className?: string; + children?: React.ReactNode; + avatar?: SkeletonAvatarProps | boolean; + title?: SkeletonTitleProps | boolean; + paragraph?: SkeletonParagraphProps | boolean; +} + +function getComponentProps(prop: T | boolean | undefined): T | {} { + if (prop && typeof prop === 'object') { + return prop; + } + return {}; +} + +function getAvatarBasicProps(hasTitle: boolean, hasParagraph: boolean): SkeletonAvatarProps { + if (hasTitle && !hasParagraph) { + return { shape: 'square' }; + } + + return { shape: 'circle' }; +} + +function getTitleBasicProps(hasAvatar: boolean, hasParagraph: boolean): SkeletonTitleProps { + if (!hasAvatar && hasParagraph) { + return { width: '38%' }; + } + + if (hasAvatar && hasParagraph) { + return { width: '50%' }; + } + + return { width: '100%' }; +} + +function getParagraphBasicProps(hasAvatar: boolean, hasTitle: boolean): SkeletonParagraphProps { + const basicProps: SkeletonParagraphProps = {}; + + // Width + if (hasAvatar && hasTitle) { + basicProps.width = '100%'; + } else { + basicProps.width = '61%'; + } + + // Rows + if (!hasAvatar && hasTitle) { + basicProps.rows = 3; + } else { + basicProps.rows = 2; + } + + return basicProps; +} + +class Skeleton extends React.Component { + static defaultProps: Partial = { + prefixCls: 'ant-skeleton', + avatar: false, + title: true, + paragraph: true, + }; + + render() { + const { + loading, prefixCls, className, children, + avatar, title, paragraph, active, + } = this.props; + + if (loading || !('loading' in this.props)) { + const hasAvatar = !!avatar; + const hasTitle = !!title; + const hasParagraph = !!paragraph; + + // Avatar + let avatarNode; + if (hasAvatar) { + const avatarProps: SkeletonAvatarProps = { + ...getAvatarBasicProps(hasTitle, hasParagraph), + ...getComponentProps(avatar), + }; + + avatarNode = ( +
+ +
+ ); + } + + let contentNode; + if (hasTitle || hasParagraph) { + // Title + let $title; + if (hasTitle) { + const titleProps: SkeletonTitleProps = { + ...getTitleBasicProps(hasAvatar, hasParagraph), + ...getComponentProps(title), + }; + + $title = ( + + ); + } + + // Paragraph + let paragraphNode; + if (hasParagraph) { + const paragraphProps: SkeletonParagraphProps = { + ...getParagraphBasicProps(hasAvatar, hasTitle), + ...getComponentProps(paragraph), + }; + + paragraphNode = ( + <Paragraph {...paragraphProps} /> + ); + } + + contentNode = ( + <div className={`${prefixCls}-content`}> + {$title} + {paragraphNode} + </div> + ); + } + + const cls = classNames( + prefixCls, + className, { + [`${prefixCls}-with-avatar`]: hasAvatar, + [`${prefixCls}-active`]: active, + }, + ); + + return ( + <div className={cls}> + {avatarNode} + {contentNode} + </div> + ); + } + + return children; + } +} + +export default Skeleton; diff --git a/components/skeleton/index.zh-CN.md b/components/skeleton/index.zh-CN.md new file mode 100644 index 0000000000..f515513827 --- /dev/null +++ b/components/skeleton/index.zh-CN.md @@ -0,0 +1,46 @@ +--- +category: Components +subtitle: 加载占位图 +type: Data Entry +title: Skeleton +cols: 1 +--- + +在需要等待加载内容的位置提供一个占位图。 + +## 何时使用 + +- 网络较慢,需要长时间等待加载处理的情况下。 +- 图文信息内容较多的列表/卡片中。 + +## API + +### Skeleton + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| active | 是否展示动画效果 | boolean | false | +| avatar | 是否显示头像占位图 | boolean \| [SkeletonAvatarProps](#SkeletonAvatarProps) | false | +| loading | 为 `true` 时,显示占位图。反之则直接展示子组件 | boolean | - | +| paragraph | 是否显示段落占位图 | boolean \| [SkeletonParagraphProps](#SkeletonParagraphProps) | true | +| title | 是否显示标题占位图 | boolean \| [SkeletonTitleProps](#SkeletonTitleProps) | true | + +### SkeletonAvatarProps + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| size | 设置头像占位图的大小 | Enum{ 'large', 'small', 'default' } | - | +| shape | 指定头像的形状 | Enum{ 'circle', 'square' } | - | + +### SkeletonTitleProps + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| width | 设置标题占位图的宽度 | number \| string | - | + +### SkeletonParagraphProps + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| rows | 设置段落占位图的行数 | number | - | +| width | 设置标题占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度 | number \| string \| Array<number \| string> | - | diff --git a/components/skeleton/style/index.less b/components/skeleton/style/index.less new file mode 100644 index 0000000000..b2e0ec93e6 --- /dev/null +++ b/components/skeleton/style/index.less @@ -0,0 +1,113 @@ +@import "../../style/themes/default"; +@import "../../style/mixins/index"; + +@skeleton-prefix-cls: ~"@{ant-prefix}-skeleton"; +@skeleton-avatar-prefix-cls: ~"@{skeleton-prefix-cls}-avatar"; +@skeleton-title-prefix-cls: ~"@{skeleton-prefix-cls}-title"; +@skeleton-paragraph-prefix-cls: ~"@{skeleton-prefix-cls}-paragraph"; + +@skeleton-to-color: shade(@skeleton-color, 5%); + +.@{skeleton-prefix-cls} { + display: table; + width: 100%; + + &-header { + display: table-cell; + vertical-align: top; + padding-right: 16px; + + // Avatar + .@{skeleton-avatar-prefix-cls} { + display: inline-block; + vertical-align: top; + background: @skeleton-color; + + .avatar-size(@avatar-size-base); + + &-lg { + .avatar-size(@avatar-size-lg); + } + + &-sm { + .avatar-size(@avatar-size-sm); + } + } + } + + &-content { + display: table-cell; + vertical-align: top; + width: 100%; + + // Title + .@{skeleton-title-prefix-cls} { + margin-top: 16px; + height: 16px; + width: 100%; + background: @skeleton-color; + + + .@{skeleton-paragraph-prefix-cls} { + margin-top: 24px; + } + } + + // paragraph + .@{skeleton-paragraph-prefix-cls} { + > li { + height: 16px; + background: @skeleton-color; + + + li { + margin-top: 16px; + } + } + } + } + + &-with-avatar &-content { + // Title + .@{skeleton-title-prefix-cls} { + margin-top: 12px; + + + .@{skeleton-paragraph-prefix-cls} { + margin-top: 28px; + } + } + } + + // With active animation + &.@{skeleton-prefix-cls}-active { + & .@{skeleton-prefix-cls}-content { + .@{skeleton-title-prefix-cls}, + .@{skeleton-paragraph-prefix-cls} > li { + .skeleton-color(); + } + } + } +} + +.avatar-size(@size) { + width: @size; + height: @size; + line-height: @size; + + &.@{skeleton-avatar-prefix-cls}-circle { + border-radius: 50%; + } +} + +.skeleton-color() { + background: linear-gradient(90deg, @skeleton-color 25%, @skeleton-to-color 37%, @skeleton-color 63%); + animation: ~"@{skeleton-prefix-cls}-loading" 1.4s ease infinite; + background-size: 400% 100%; +} + +@keyframes ~"@{skeleton-prefix-cls}-loading" { + 0% { + background-position: 100% 50%; + } + 100% { + background-position: 0 50%; + } +} diff --git a/components/skeleton/style/index.tsx b/components/skeleton/style/index.tsx new file mode 100644 index 0000000000..3a3ab0de59 --- /dev/null +++ b/components/skeleton/style/index.tsx @@ -0,0 +1,2 @@ +import '../../style/index.less'; +import './index.less'; diff --git a/components/spin/index.zh-CN.md b/components/spin/index.zh-CN.md index d3f01600a0..95a434913f 100644 --- a/components/spin/index.zh-CN.md +++ b/components/spin/index.zh-CN.md @@ -18,7 +18,7 @@ subtitle: 加载中 | delay | 延迟显示加载效果的时间(防止闪烁) | number (毫秒) | - | | indicator | 加载指示符 | ReactElement | - | | size | 组件大小,可选值为 `small` `default` `large` | string | 'default' | -| spinning | 是否旋转 | boolean | true | +| spinning | 是否为加载中状态 | boolean | true | | tip | 当作为包裹元素时,可以自定义描述文案 | string | - | | wrapperClassName | 包装器的类属性 | string | - | diff --git a/components/steps/index.en-US.md b/components/steps/index.en-US.md index 00823e19a8..b9acb4283a 100644 --- a/components/steps/index.en-US.md +++ b/components/steps/index.en-US.md @@ -33,6 +33,7 @@ The whole of the step bar. | progressDot | Steps with progress dot style, customize the progress dot by setting it to a function. labelPlacement will be `vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false | | size | to specify the size of the step bar, `default` and `small` are currently supported | string | `default` | | status | to specify the status of current step, can be set to one of the following values: `wait` `process` `finish` `error` | string | `process` | +| initial | set the initial step, counting from 0 | number | 0 | ### Steps.Step diff --git a/components/steps/index.tsx b/components/steps/index.tsx index bdbd71ea54..5a8f85fbcc 100644 --- a/components/steps/index.tsx +++ b/components/steps/index.tsx @@ -6,6 +6,7 @@ export interface StepsProps { prefixCls?: string; iconPrefix?: string; current?: number; + initial?: number; status?: 'wait' | 'process' | 'finish' | 'error'; size?: 'default' | 'small'; direction?: 'horizontal' | 'vertical'; diff --git a/components/steps/index.zh-CN.md b/components/steps/index.zh-CN.md index 56fc397e91..c7c32466bc 100644 --- a/components/steps/index.zh-CN.md +++ b/components/steps/index.zh-CN.md @@ -34,6 +34,7 @@ title: Steps | progressDot | 点状步骤条,可以设置为一个 function,labelPlacement 将强制为`vertical` | Boolean or (iconDot, {index, status, title, description}) => ReactNode | false | | size | 指定大小,目前支持普通(`default`)和迷你(`small`) | string | default | | status | 指定当前步骤的状态,可选 `wait` `process` `finish` `error` | string | process | +| initial | 起始序号,从 0 开始记数 | number | 0 | ### Steps.Step diff --git a/components/style/core/motion/other.less b/components/style/core/motion/other.less index 310fb4bc25..606529686d 100644 --- a/components/style/core/motion/other.less +++ b/components/style/core/motion/other.less @@ -19,14 +19,14 @@ right: -1px; border-radius: inherit; border: 0 solid @primary-color; - opacity: 0.4; - animation: waveEffect .4s cubic-bezier(.25, .8, .25, 1); + opacity: 0.2; + animation: fadeEffect 2.4s @ease-out-circ, waveEffect .48s @ease-out-circ; + animation-fill-mode: forwards; display: block; } @keyframes waveEffect { - to { - opacity: 0; + 100% { top: -@wave-animation-width; left: -@wave-animation-width; bottom: -@wave-animation-width; @@ -34,3 +34,9 @@ border-width: @wave-animation-width; } } + +@keyframes fadeEffect { + 100% { + opacity: 0; + } +} diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 81625aa2f0..c65b0b65a1 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -484,6 +484,10 @@ @collapse-content-padding: @padding-md; @collapse-content-bg: @component-background; +// Skeleton +// --- +@skeleton-color: #f2f2f2; + // Message // --- @message-notice-content-padding: 10px 16px; diff --git a/components/tooltip/style/index.less b/components/tooltip/style/index.less index b54ed7c5b4..c272377532 100644 --- a/components/tooltip/style/index.less +++ b/components/tooltip/style/index.less @@ -47,7 +47,7 @@ border-radius: @border-radius-base; box-shadow: @box-shadow-base; min-height: 32px; - word-break: break-all; + word-wrap: break-word; } // Arrows diff --git a/components/tree-select/interface.tsx b/components/tree-select/interface.tsx index 86a7438d9c..3f1dad9aca 100644 --- a/components/tree-select/interface.tsx +++ b/components/tree-select/interface.tsx @@ -3,7 +3,7 @@ import { AbstractSelectProps } from '../select'; export type TreeNode = TreeNodeNormal | TreeNodeSimpleMode; -interface TreeNodeNormal { +export interface TreeNodeNormal { value: string; /** * @deprecated Please use `title` instead. @@ -18,12 +18,12 @@ interface TreeNodeNormal { children?: TreeNodeNormal[]; } -interface TreeNodeSimpleMode { +export interface TreeNodeSimpleMode { /* It is possible to change `id` and `pId` prop keys using TreeDataSimpleMode so those keys can be anything */ [key: string]: string | boolean | React.ReactNode; } -interface TreeDataSimpleMode { +export interface TreeDataSimpleMode { id?: string; pId?: string; rootPId?: string; diff --git a/components/tree/Tree.tsx b/components/tree/Tree.tsx index 3d067148cc..66a8907749 100644 --- a/components/tree/Tree.tsx +++ b/components/tree/Tree.tsx @@ -39,6 +39,8 @@ export interface AntTreeNodeProps { selectable?: boolean; icon?: ((treeNode: AntdTreeNodeAttribute) => React.ReactNode) | React.ReactNode; children?: React.ReactNode; + + [customProp: string]: any; } export interface AntTreeNode extends React.Component<AntTreeNodeProps, {}> { } diff --git a/components/upload/interface.tsx b/components/upload/interface.tsx index dc8841a5d3..bd82edd1bc 100755 --- a/components/upload/interface.tsx +++ b/components/upload/interface.tsx @@ -22,7 +22,6 @@ export interface UploadFile { status?: UploadFileStatus; percent?: number; thumbUrl?: string; - isNotImage?: boolean; originFileObj?: File; response?: any; error?: any; diff --git a/docs/react/customize-theme.en-US.md b/docs/react/customize-theme.en-US.md index 8bbe66f454..b3131071b4 100644 --- a/docs/react/customize-theme.en-US.md +++ b/docs/react/customize-theme.en-US.md @@ -7,57 +7,106 @@ Ant Design allows you to customize some basic design aspects in order to meet th ![customized themes](https://zos.alipayobjects.com/rmsportal/zTFoszBtDODhXfLAazfSpYbSLSEeytoG.png) -## Less variables +## Ant Design Less variables We are using [Less](http://lesscss.org/) as the development language for styling. A set of less variables are defined for each design aspect that can be customized to your needs. -- [Default Variables](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less) +There are some major variables below, all less variables could be found in [Default Variables](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less). + +```less +@primary-color: #1890ff; // primary color for all components +@link-color: #1890ff; // link color +@success-color: #52c41a; // success state color +@warning-color: #faad14; // warning state color +@error-color: #f5222d; // error state color +@font-size-base: 14px; // major text font size +@heading-color: rgba(0, 0, 0, .85); // heading text color +@text-color: rgba(0, 0, 0, .65); // major text color +@text-color-secondary : rgba(0, 0, 0, .45); // secondary text color +@disabled-color : rgba(0, 0, 0, .25); // disable state color +@border-radius-base: 4px; // major border radius +@border-color-base: #d9d9d9; // major border color +@box-shadow-base: 0 2px 8px rgba(0, 0, 0, .15); // major shadow for layers +``` Please report an issue if the existing list of variables is not enough for you. ## How to do it -We recommend [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) to override the default values of the variables. There are two ways to achieve it in practice. +We will use [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) provided by less.js to override the default values of the variables, You can use this [example](https://github.com/ant-design/create-react-app-antd) as a live playground. We now introduce some popular way to do it depends on different workflow. -You can use this [example](https://github.com/ant-design/antd-init/tree/master/examples/customize-antd-theme) as a playground. +### Customize in webpack -### 1) Using `theme` property (recommended way) +We take a typical `webpack.config.js` file as example to customize it's [less-loader](https://github.com/webpack-contrib/less-loader) options. + +```diff +// webpack.config.js +module.exports = { + rules: [{ + test: /\.less$/, + use: [{ + loader: 'style-loader', + }, { + loader: 'css-loader', // translates CSS into CommonJS + }, { + loader: 'less-loader', // compiles Less to CSS ++ options: { ++ modifyVars: { ++ 'primary-color': '#1DA57A', ++ 'link-color': '#1DA57A', ++ 'border-radius-base': '2px', ++ }, ++ javascriptEnabled: true, ++ }, + }], + // ...other rules + }], + // ...other config +} +``` + +Note that do not exclude antd package in node_modules when using less-loader. + +### Customize in roadhog or Umi + +You can easily use `theme` field in `.webpackrc` file of your project root directory if you are using [roadhog](https://github.com/sorrycc/roadhog) or [Umi](http://umijs.org/),which could be a object or a javascript file path. -Specify the `theme` property in the `package.json` or `.webpackrc` file, whose value can be either an object or the path to a JS file that contains the custom values of specific variables: -- example of directly specifying the custom values as an object: ```js "theme": { "primary-color": "#1DA57A", }, ``` -- example of specifying a [file path](https://github.com/ant-design/antd-init/blob/master/examples/customize-antd-theme/theme.js) to a JS file: + +Or just [a javascript file path](https://github.com/ant-design/ant-design-pro/blob/3c2a056ef0dac06ce3b4389192691bb1f5c448e2/.webpackrc.js#L19): + ```js "theme": "./theme.js", ``` -This approach is available only when using [antd-init](https://github.com/ant-design/antd-init) or [dva-cli](https://github.com/dvajs/dva-cli). If you choose other boilerplates, you can write a webpack config about [less-loader modifyVars](https://github.com/webpack/less-loader#less-options) like [atool-build ](https://github.com/ant-tool/atool-build/blob/a4b3e3eec4ffc09b0e2352d7f9d279c4c28fdb99/src/getWebpackCommonConfig.js#L131-L138) does. +### Customize in create-react-app -Note: +Follow [Use in create-react-app](/docs/react/create-react-app). -- Importing styles from less files is necessary. - - If you import styles by specifying the `style` option of [babel-plugin-import](https://github.com/ant-design/babel-plugin-import), change it from `'css'` to `true`, which will import the `less` version of antd. - - If you import styles from `'antd/dist/antd.css'`, change it to `antd/dist/antd.less`. -- When using `dva-cli@0.7.0+`, you should add the `theme` block to [.roadhogrc](https://github.com/dvajs/dva-example-user-dashboard/commit/d6da33b3a6e18eb7f003752a4b00b5a660747c31) instead of `package.json`. -- If you want to override `@icon-url`, the value must be contained in quotes like `"@icon-url": "'your-icon-font-path'"` ([A fix sample](https://github.com/visvadw/dvajs-user-dashboard/pull/2)). +### Customize in less file -### 2) Overriding Less variables (alternative way) +Another approach to customize theme is creating a `less` file within variables to override `antd.less`. -Override variables via less definition files. - -Create a standalone less file like the one below, and import it in your project. - - ```css - @import "~antd/dist/antd.less"; // import official less entry file - @import "your-theme-file.less"; // override variables here - ``` +```css +@import "~antd/dist/antd.less"; // Import Ant Design styles by less entry +@import "your-theme-file.less"; // variables to override above +``` Note: This way will load the styles of all components, regardless of your demand, which cause `style` option of `babel-plugin-import` not working. +## Not working? + +You must import styles as less format. A common mistake would be importing multiple copied of styles that some of them are css format to override the less styles. + +- If you import styles by specifying the `style` option of [babel-plugin-import](https://github.com/ant-design/babel-plugin-import), change it from `'css'` to `true`, which will import the `less` version of antd. +- If you import styles from `'antd/dist/antd.css'`, change it to `antd/dist/antd.less`. + +If you want to override `@icon-url`, the value must be contained in quotes like `"@icon-url": "'your-icon-font-path'"` ([A fix sample](https://github.com/visvadw/dvajs-user-dashboard/pull/2)). + ## Related Articles - [Using Ant Design in Sass-Styled Webpack Projects with `antd-scss-theme-plugin`](https://intoli.com/blog/antd-scss-theme-plugin/) diff --git a/docs/react/customize-theme.zh-CN.md b/docs/react/customize-theme.zh-CN.md index fe61ee9eb4..67efa1b878 100644 --- a/docs/react/customize-theme.zh-CN.md +++ b/docs/react/customize-theme.zh-CN.md @@ -7,24 +7,69 @@ Ant Design 设计规范上支持一定程度的样式定制,以满足业务和 ![一些配置好的主题](https://zos.alipayobjects.com/rmsportal/zTFoszBtDODhXfLAazfSpYbSLSEeytoG.png) -## 样式变量 +## Ant Design 的样式变量 antd 的样式使用了 [Less](http://lesscss.org/) 作为开发语言,并定义了一系列全局/组件的样式变量,你可以根据需求进行相应调整。 -- [默认样式变量](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less) +以下是一些最常用的通用变量,所有样式变量可以在 [这里](https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less) 找到。 + +```less +@primary-color: #1890ff; // 全局主色 +@link-color: #1890ff; // 链接色 +@success-color: #52c41a; // 成功色 +@warning-color: #faad14; // 警告色 +@error-color: #f5222d; // 错误色 +@font-size-base: 14px; // 主字号 +@heading-color: rgba(0, 0, 0, .85); // 标题色 +@text-color: rgba(0, 0, 0, .65); // 主文本色 +@text-color-secondary : rgba(0, 0, 0, .45); // 次文本色 +@disabled-color : rgba(0, 0, 0, .25); // 失效色 +@border-radius-base: 4px; // 组件/浮层圆角 +@border-color-base: #d9d9d9; // 边框色 +@box-shadow-base: 0 2px 8px rgba(0, 0, 0, .15); // 浮层阴影 +``` 如果以上变量不能满足你的定制需求,可以给我们提 issue。 ## 定制方式 -我们使用 [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) 的方式来覆盖变量。 -在具体工程实践中,有 `package.theme` 和 `less` 两种方案,选择一种即可。 +原理上是使用 less 提供的 [modifyVars](http://lesscss.org/usage/#using-less-in-the-browser-modify-variables) 的方式进行覆盖变量,可以在本地运行 [例子](https://github.com/ant-design/create-react-app-antd) 查看定制效果。下面将针对不同的场景提供一些常用的定制方式。 -可以在本地运行 [例子](https://github.com/ant-design/antd-init/tree/master/examples/customize-antd-theme) 查看定制效果。 +### 在 webpack 中定制主题 -### 1) theme 属性(推荐) +我们以 webpack@4 为例进行说明,以下是一个 `webpack.config.js` 的典型例子,对 [less-loader](https://github.com/webpack-contrib/less-loader) 的 options 属性进行相应配置。 -配置在 `package.json` 或 `.webpackrc` 下的 `theme` 字段。theme 可以配置为一个对象或文件路径。 +```diff +// webpack.config.js +module.exports = { + rules: [{ + test: /\.less$/, + use: [{ + loader: 'style-loader', + }, { + loader: 'css-loader', // translates CSS into CommonJS + }, { + loader: 'less-loader', // compiles Less to CSS ++ options: { ++ modifyVars: { ++ 'primary-color': '#1DA57A', ++ 'link-color': '#1DA57A', ++ 'border-radius-base': '2px', ++ }, ++ javascriptEnabled: true, ++ }, + }], + // ...other rules + }], + // ...other config +} +``` + +注意 less-loader 的处理范围不要过滤掉 `node_modules` 下的 antd 包。 + +### 在 roadhog 或 Umi 里配置主题 + +如果你在使用 [roadhog](https://github.com/sorrycc/roadhog) 或者 [Umi](http://umijs.org/),那么可以很方便地在项目根目录的 `.webpackrc` 文件中 `theme` 字段进行主题配置。`theme` 可以配置为一个对象或文件路径。 ```js "theme": { @@ -32,34 +77,35 @@ antd 的样式使用了 [Less](http://lesscss.org/) 作为开发语言,并定 }, ``` -或者 [一个 js 文件](https://github.com/ant-design/antd-init/blob/master/examples/customize-antd-theme/theme.js): +或者 [一个 js 文件](https://github.com/ant-design/ant-design-pro/blob/3c2a056ef0dac06ce3b4389192691bb1f5c448e2/.webpackrc.js#L19): ```js "theme": "./theme.js", ``` -定义 `theme` 属性时,需要配合使用([antd-init](https://github.com/ant-design/antd-init) 或 [dva-cli](https://github.com/dvajs/dva-cli)。如果你使用的是其他脚手架,可以参考 [atool-build 中 less-loader 的 webpack 相关配置 ](https://github.com/ant-tool/atool-build/blob/a4b3e3eec4ffc09b0e2352d7f9d279c4c28fdb99/src/getWebpackCommonConfig.js#L131-L138),利用 [less-loader](https://github.com/webpack/less-loader#less-options) 的 `modifyVars` 配置来覆盖原来的样式变量。 +### 在 create-react-app 中定制主题 -注意: +参考 [在 create-react-app 中使用](/docs/react/create-react-app) 进行配置即可。 -- 样式必须加载 less 格式。 - - 如果你在使用 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 的 `style` 配置来引入样式,需要将配置值从 `'css'` 改为 `true`,这样会引入 less 文件。 - - 如果你是通过 `'antd/dist/antd.css'` 引入样式的,改为 `antd/dist/antd.less`。 -- `dva-cli@0.7.0+` 的 `theme` 属性需要写在 [.roadhogrc](https://github.com/dvajs/dva-example-user-dashboard/commit/d6da33b3a6e18eb7f003752a4b00b5a660747c31) 文件里。 -- 如果要覆盖 `@icon-url` 变量,内容需要包括引号 `"@icon-url": "'your-icon-font-path'"`([修正示例](https://github.com/visvadw/dvajs-user-dashboard/pull/2))。 +### 配置 less 变量文件 -### 2) less +另外一种方式是建立一个单独的 `less` 变量文件,引入这个文件覆盖 `antd.less` 里的变量。 -用 less 文件进行变量覆盖。 +```css +@import "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 +@import "your-theme-file.less"; // 用于覆盖上面定义的变量 +``` -建立一个单独的 `less` 文件如下,再引入这个文件。 +注意,这种方式已经载入了所有组件的样式,不需要也无法和按需加载插件 `babel-plugin-import` 的 `style` 属性一起使用。 - ```css - @import "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 - @import "your-theme-file.less"; // 用于覆盖上面定义的变量 - ``` +## 没有生效? -注意:这种方式已经载入了所有组件的样式,不需要也无法和按需加载插件 `babel-plugin-import` 的 `style` 属性一起使用。 +注意样式必须加载 less 格式,一个常见的问题就是引入了多份样式,less 的样式被 css 的样式覆盖了。 + +- 如果你在使用 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import) 的 `style` 配置来引入样式,需要将配置值从 `'css'` 改为 `true`,这样会引入 less 文件。 +- 如果你是通过 `'antd/dist/antd.css'` 引入样式的,改为 `antd/dist/antd.less`。 + +如果要覆盖 `@icon-url` 变量,内容需要包括引号 `"@icon-url": "'your-icon-font-path'"`([修正示例](https://github.com/visvadw/dvajs-user-dashboard/pull/2))。 ## 社区教程 diff --git a/site/theme/zh-CN.js b/site/theme/zh-CN.js index dd1dcf85b3..7333ae851d 100644 --- a/site/theme/zh-CN.js +++ b/site/theme/zh-CN.js @@ -75,7 +75,7 @@ module.exports = { 'app.footer.company': 'AFX', 'app.footer.ant-design': '蚂蚁 UI 体系', 'app.footer.yuque': '语雀', - 'app.footer.yuque.slogan': '企业文档管理平台', + 'app.footer.yuque.slogan': '知识创作·协作平台', 'app.footer.fengdie': '云凤蝶', 'app.footer.fengdie.slogan': '移动建站平台', 'app.footer.zhihu': '知乎专栏', diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index 1f82a35b7c..d0acf60bcc 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -41,6 +41,7 @@ Array [ "Rate", "Row", "Select", + "Skeleton", "Slider", "Spin", "Steps",