diff --git a/components/__tests__/__snapshots__/index.test.js.snap b/components/__tests__/__snapshots__/index.test.js.snap index e2261c093..2e507f928 100644 --- a/components/__tests__/__snapshots__/index.test.js.snap +++ b/components/__tests__/__snapshots__/index.test.js.snap @@ -6,6 +6,7 @@ Array [ "Anchor", "AutoComplete", "Alert", + "Avatar", "BackTop", "Badge", "Breadcrumb", diff --git a/components/avatar/__tests__/Avatar.test.js b/components/avatar/__tests__/Avatar.test.js new file mode 100644 index 000000000..685cb1422 --- /dev/null +++ b/components/avatar/__tests__/Avatar.test.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Avatar from '..'; + +describe('Avatar Render', () => { + it('Render long string correctly', () => { + const wrapper = mount(TestString); + const children = wrapper.find('.ant-avatar-string'); + expect(children.length).toBe(1); + }); +}); diff --git a/components/avatar/__tests__/__snapshots__/demo.test.js.snap b/components/avatar/__tests__/__snapshots__/demo.test.js.snap new file mode 100644 index 000000000..34f60465b --- /dev/null +++ b/components/avatar/__tests__/__snapshots__/demo.test.js.snap @@ -0,0 +1,389 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/avatar/demo/badge.md correctly 1`] = ` +
+ + + + + + + +

+ 0 +

+

+ 1 +

+

+ 2 +

+

+ 3 +

+

+ 4 +

+

+ 5 +

+

+ 6 +

+

+ 7 +

+

+ 8 +

+

+ 9 +

+

+ 0 +

+

+ 1 +

+

+ 2 +

+

+ 3 +

+

+ 4 +

+

+ 5 +

+

+ 6 +

+

+ 7 +

+

+ 8 +

+

+ 9 +

+

+ 0 +

+

+ 1 +

+

+ 2 +

+

+ 3 +

+

+ 4 +

+

+ 5 +

+

+ 6 +

+

+ 7 +

+

+ 8 +

+

+ 9 +

+
+
+
+
+ + + + + + + + +
+`; + +exports[`renders ./components/avatar/demo/basic.md correctly 1`] = ` +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+`; + +exports[`renders ./components/avatar/demo/dynamic.md correctly 1`] = ` +
+ + + U + + + +
+`; + +exports[`renders ./components/avatar/demo/type.md correctly 1`] = ` +
+
+
+ + + +
+
+ + + U + + +
+
+ + + USER + + +
+
+ + + +
+
+
+
+ + + U + + +
+
+ + + +
+
+
+`; diff --git a/components/avatar/__tests__/demo.test.js b/components/avatar/__tests__/demo.test.js new file mode 100644 index 000000000..bb02eea33 --- /dev/null +++ b/components/avatar/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('avatar'); diff --git a/components/avatar/demo/badge.md b/components/avatar/demo/badge.md new file mode 100644 index 000000000..11d7b1074 --- /dev/null +++ b/components/avatar/demo/badge.md @@ -0,0 +1,29 @@ +--- +order: 3 +title: + zh-CN: 带徽标的头像 + en-US: With Badge +--- + +## zh-CN + +通常用于消息提示。 + +## en-US + +Usually used for messages remind. + +````jsx +import { Avatar, Badge } from 'antd'; + +ReactDOM.render( +
+ + + + + + +
+, mountNode); +```` diff --git a/components/avatar/demo/basic.md b/components/avatar/demo/basic.md new file mode 100644 index 000000000..d67a9ac36 --- /dev/null +++ b/components/avatar/demo/basic.md @@ -0,0 +1,33 @@ +--- +order: 0 +title: + zh-CN: 基本 + en-US: Basic +--- + +## zh-CN + +头像有三种尺寸,两种形状可选。 + +## en-US + +Three sizes and two shapes are available. + +````jsx +import { Avatar, Row, Col } from 'antd'; + +ReactDOM.render( +
+ + + + + + + + + + +
+, mountNode); +```` diff --git a/components/avatar/demo/dynamic.md b/components/avatar/demo/dynamic.md new file mode 100644 index 000000000..c4eda2706 --- /dev/null +++ b/components/avatar/demo/dynamic.md @@ -0,0 +1,49 @@ +--- +order: 2 +title: + zh-CN: 自动调整字符大小 + en-US: Autoset Font Size +--- + +## zh-CN + +对于字符型的头像,当字符串较长时,字体大小可以根据头像宽度自动调整。 + +## en-US + +For letter type Avatar, when the letters are too long to display, the font size can be automatically adjusted according to the width of the Avatar. + +````jsx +import { Avatar, Button } from 'antd'; + +const UserList = ['U', 'Lucy', 'Tom', 'Edward']; +const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae']; + +class Autoset extends React.Component { + constructor(props) { + super(props); + this.state = { + user: UserList[0], + color: colorList[0], + }; + } + changeUser = () => { + const index = UserList.indexOf(this.state.user); + this.setState({ + user: index < UserList.length - 1 ? UserList[index + 1] : UserList[0], + color: index < colorList.length - 1 ? colorList[index + 1] : colorList[0], + }); + } + render() { + return ( +
+ {this.state.user} + +
+ ); + } +} + +ReactDOM.render( +, mountNode); +```` diff --git a/components/avatar/demo/type.md b/components/avatar/demo/type.md new file mode 100644 index 000000000..e24ab3386 --- /dev/null +++ b/components/avatar/demo/type.md @@ -0,0 +1,33 @@ +--- +order: 1 +title: + zh-CN: 类型 + en-US: Type +--- + +## zh-CN + +支持三种类型:图片、Icon 以及字符,其中 Icon 和字符型可以自定义图标颜色及背景色。 + +## en-US + +Image, Icon and letter are supported, and the latter two kinds avatar can have custom colors and background colors. + +````jsx +import { Avatar, Row, Col } from 'antd'; + +ReactDOM.render( +
+ + + U + USER + + + + U + + +
+, mountNode); +```` diff --git a/components/avatar/index.en-US.md b/components/avatar/index.en-US.md new file mode 100644 index 000000000..d16865689 --- /dev/null +++ b/components/avatar/index.en-US.md @@ -0,0 +1,16 @@ +--- +category: Components +type: Data Display +title: Avatar +--- + +Avatars can be used to represent people or object, which supports image, antd-Icon, or letter. + +## API + +| Property | Description | Type | Default | +|----------- |--------------------------------------------------------- | ---------- |-------| +| type | to set the shape of avatar | Enum{ 'circle', 'square' } | `circle` | +| size | to set the size of avatar | Enum{ 'large', 'small', 'default' } | `default` | +| src | the address of a image avatar | string | - | +| icon | the icon type of a icon avatar, see `Icon` Component | string | - | diff --git a/components/avatar/index.tsx b/components/avatar/index.tsx new file mode 100644 index 000000000..4320b0539 --- /dev/null +++ b/components/avatar/index.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Icon from '../icon'; +import classNames from 'classnames'; + +export interface AvatarProps { + /** Shape of avatar, options:`circle`, `square` */ + shape?: 'circle' | 'square'; + /** Size of avatar, options:`large`, `small`, `default` */ + size?: 'large' | 'small' | 'default'; + /** Src of image avatar */ + src?: string; + /** Type of the Icon to be used in avatar */ + icon?: string; + style?: React.CSSProperties; + prefixCls?: string; + className?: string; + children?: any; +} + +export default class Avatar extends React.Component { + static defaultProps = { + prefixCls: 'ant-avatar', + shape: 'circle', + size: 'default', + }; + + private avatarChildren: any; + + constructor(props) { + super(props); + this.state = { + scale: 1, + }; + } + + componentDidMount() { + this.setScale(); + } + + componentDidUpdate(prevProps, prevState) { + if (prevProps.children !== this.props.children + || (prevState.scale !== this.state.scale && this.state.scale === 1)) { + this.setScale(); + } + } + + setScale = () => { + const childrenNode = this.avatarChildren; + if (childrenNode) { + const childrenWidth = childrenNode.offsetWidth; + const avatarWidth = ReactDOM.findDOMNode(this).getBoundingClientRect().width; + // add 4px gap for each side to get better performance + if (avatarWidth - 8 < childrenWidth) { + this.setState({ + scale: (avatarWidth - 8) / childrenWidth, + }); + } else { + this.setState({ + scale: 1, + }); + } + } + } + + render() { + const { + prefixCls, shape, size, src, icon, className, ...others, + } = this.props; + + const sizeCls = classNames({ + [`${prefixCls}-lg`]: size === 'large', + [`${prefixCls}-sm`]: size === 'small', + }); + + const classString = classNames(prefixCls, className, sizeCls, { + [`${prefixCls}-${shape}`]: shape, + [`${prefixCls}-image`]: src, + [`${prefixCls}-icon`]: icon, + }); + + let children = this.props.children; + if (src) { + children = ; + } else if (icon) { + children = ; + } else { + const childrenNode = this.avatarChildren; + if (childrenNode || this.state.scale !== 1) { + const childrenStyle: React.CSSProperties = { + msTransform: `scale(${this.state.scale})`, + WebkitTransform: `scale(${this.state.scale})`, + transform: `scale(${this.state.scale})`, + position: 'absolute', + display: 'inline-block', + left: `calc(50% - ${Math.round(childrenNode.offsetWidth / 2)}px)`, + }; + children = this.avatarChildren = span} + style={childrenStyle} + > + {children} + ; + } else { + children = this.avatarChildren = span} + > + {children} + ; + } + } + return ( + + {children} + + ); + } +} diff --git a/components/avatar/index.zh-CN.md b/components/avatar/index.zh-CN.md new file mode 100644 index 000000000..a9c45e59a --- /dev/null +++ b/components/avatar/index.zh-CN.md @@ -0,0 +1,17 @@ +--- +category: Components +subtitle: 头像 +type: Data Display +title: Avatar +--- + +用来代表用户或事物,支持图片、图标或字符展示。 + +## API + +| 参数 | 说明 | 类型 | 默认值 | +|----------- |--------------------------------------------------------- | ---------- | ------- | +| shape | 指定头像的形状 | Enum{ 'circle', 'square' } | `circle` | +| size | 设置头像的大小 | Enum{ 'large', 'small', 'default' } | `default` | +| src | 图片类头像的资源地址 | string | - | +| icon | 设置头像的图标类型,参考 `Icon` 组件 | string | - | diff --git a/components/avatar/style/index.less b/components/avatar/style/index.less new file mode 100644 index 000000000..cf9fce56c --- /dev/null +++ b/components/avatar/style/index.less @@ -0,0 +1,47 @@ +@import "../../style/themes/default"; + +@avatar-prefix-cls: ~"@{ant-prefix}-avatar"; + +.@{avatar-prefix-cls} { + display: inline-block; + text-align: center; + background: @avatar-bg; + color: @avatar-color; + white-space: nowrap; + position: relative; + overflow: hidden; + + .avatar-size(@avatar-size-base, @avatar-font-size-base); + + &-lg { + .avatar-size(@avatar-size-lg, @avatar-font-size-lg); + } + + &-sm { + .avatar-size(@avatar-size-sm, @avatar-font-size-sm); + } + + &-square { + border-radius: @avatar-border-radius; + } + + & > img { + width: 100%; + height: 100%; + } +} + +.avatar-size(@size, @font-size) { + width: @size; + height: @size; + line-height: @size; + border-radius: @size / 2; + + & > * { + line-height: @size; + } + + &.@{avatar-prefix-cls}-icon { + font-size: @font-size; + } +} diff --git a/components/avatar/style/index.tsx b/components/avatar/style/index.tsx new file mode 100644 index 000000000..3a3ab0de5 --- /dev/null +++ b/components/avatar/style/index.tsx @@ -0,0 +1,2 @@ +import '../../style/index.less'; +import './index.less'; diff --git a/components/index.tsx b/components/index.tsx index 0d951e2c9..5264e5ce4 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -19,6 +19,8 @@ export { default as AutoComplete } from './auto-complete'; export { default as Alert } from './alert'; +export { default as Avatar } from './avatar'; + export { default as BackTop } from './back-top'; export { default as Badge } from './badge'; diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 089fca9ad..b82dc1396 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -335,3 +335,14 @@ @back-top-color: #fff; @back-top-bg: rgba(64, 64, 64, 0.4); @back-top-hover-bg: rgba(64, 64, 64, 0.6); + +// Avatar +@avatar-size-base: 32px; +@avatar-size-lg: 40px; +@avatar-size-sm: 24px; +@avatar-font-size-base: 18px; +@avatar-font-size-lg: 24px; +@avatar-font-size-sm: 14px; +@avatar-bg: #ccc; +@avatar-color: #fff; +@avatar-border-radius: @border-radius-base; diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index fc474223e..ff1355fb9 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -6,6 +6,7 @@ Array [ "Anchor", "AutoComplete", "Alert", + "Avatar", "BackTop", "Badge", "Breadcrumb",