diff --git a/components.json b/components.json index 191b7bdc6e..c5445d7425 100644 --- a/components.json +++ b/components.json @@ -77,5 +77,6 @@ "backtop": "./packages/backtop/index.js", "infiniteScroll": "./packages/infiniteScroll/index.js", "page-header": "./packages/page-header/index.js", - "cascader-panel": "./packages/cascader-panel/index.js" + "cascader-panel": "./packages/cascader-panel/index.js", + "avatar": "./packages/avatar/index.js" } diff --git a/examples/components/theme/components-preview.vue b/examples/components/theme/components-preview.vue index 5e84f3050a..fc183bf593 100644 --- a/examples/components/theme/components-preview.vue +++ b/examples/components/theme/components-preview.vue @@ -25,6 +25,10 @@ .el-carousel__item:nth-child(2n + 1) { background-color: #d3dce6; } + + .el-avatar:not(:last-child) { + margin-right: 20px; + } } + +``` +::: + +### Types + +It supports images, Icons, or characters + +:::demo +```html + +``` +::: + +### Fallback when image load error + +fallback when image load error + +:::demo +```html + + + +``` +::: + +### How the image fit its container + +Set how the image fit its container for an image avatar, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). + +:::demo +```html + + + +``` +::: + +### Attributes + +| Attribute | Description | Type | Accepted Values | Default | +| ----------------- | -------------------------------- | --------------- | ------ | ------ | +| icon | set representation type to Icon, more info on Icon Component | string | | | +| size | set avatar size | number/string | number / large / medium / small | large | +| shape | set avatar shape | string | circle / square | circle | +| src | the address of the image for an image avatar | string | | | +| srcSet | A list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use | string | | | +| alt | This attribute defines an alternative text description of the image | string | | | +| fit | set how the image fit its container for an image avatar | string | fill / contain / cover / none / scale-down | cover | + +### Events + +| Event Name | Description | Parameters | +| ------ | ------------------ | -------- | +| error | handler when img load error, return false to prevent default fallback behavior |(e: Event) | + +### Slot + +| Slot Name | Description | +| default | customize avatar content | diff --git a/examples/docs/es/avatar.md b/examples/docs/es/avatar.md new file mode 100644 index 0000000000..99e91980d8 --- /dev/null +++ b/examples/docs/es/avatar.md @@ -0,0 +1,145 @@ +## Avatar avatar + +Avatars can be used to represent people or objects. It supports images, Icons, or characters. + +### Basic + +use `shape` and `size` prop to set avatar's shape and size + +:::demo +```html + + + +``` +::: + +### Types + +It supports images, Icons, or characters + +:::demo +```html + +``` +::: + +### Fallback when image load error + +fallback when image load error + +:::demo +```html + + + +``` +::: + +### How the image fit its container + +Set how the image fit its container for an image avatar, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). + +:::demo +```html + + + +``` +::: + +### Attributes + +| Attribute | Description | Type | Accepted Values | Default | +| ----------------- | -------------------------------- | --------------- | ------ | ------ | +| icon | set representation type to Icon, more info on Icon Component | string | | | +| size | set avatar size | number/string | number / large / medium / small | large | +| shape | set avatar shape | string | circle / square | circle | +| src | the address of the image for an image avatar | string | | | +| srcSet | A list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use | string | | | +| alt | This attribute defines an alternative text description of the image | string | | | +| fit | set how the image fit its container for an image avatar | string | fill / contain / cover / none / scale-down | cover | + +### Events + +| Event Name | Description | Parameters | +| ------ | ------------------ | -------- | +| error | handler when img load error, return false to prevent default fallback behavior |(e: Event) | + +### Slot + +| Slot Name | Description | +| default | customize avatar content | diff --git a/examples/docs/fr-FR/avatar.md b/examples/docs/fr-FR/avatar.md new file mode 100644 index 0000000000..99e91980d8 --- /dev/null +++ b/examples/docs/fr-FR/avatar.md @@ -0,0 +1,145 @@ +## Avatar avatar + +Avatars can be used to represent people or objects. It supports images, Icons, or characters. + +### Basic + +use `shape` and `size` prop to set avatar's shape and size + +:::demo +```html + + + +``` +::: + +### Types + +It supports images, Icons, or characters + +:::demo +```html + +``` +::: + +### Fallback when image load error + +fallback when image load error + +:::demo +```html + + + +``` +::: + +### How the image fit its container + +Set how the image fit its container for an image avatar, same as [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit). + +:::demo +```html + + + +``` +::: + +### Attributes + +| Attribute | Description | Type | Accepted Values | Default | +| ----------------- | -------------------------------- | --------------- | ------ | ------ | +| icon | set representation type to Icon, more info on Icon Component | string | | | +| size | set avatar size | number/string | number / large / medium / small | large | +| shape | set avatar shape | string | circle / square | circle | +| src | the address of the image for an image avatar | string | | | +| srcSet | A list of one or more strings separated by commas indicating a set of possible image sources for the user agent to use | string | | | +| alt | This attribute defines an alternative text description of the image | string | | | +| fit | set how the image fit its container for an image avatar | string | fill / contain / cover / none / scale-down | cover | + +### Events + +| Event Name | Description | Parameters | +| ------ | ------------------ | -------- | +| error | handler when img load error, return false to prevent default fallback behavior |(e: Event) | + +### Slot + +| Slot Name | Description | +| default | customize avatar content | diff --git a/examples/docs/zh-CN/avatar.md b/examples/docs/zh-CN/avatar.md new file mode 100644 index 0000000000..e0b5d2f6b1 --- /dev/null +++ b/examples/docs/zh-CN/avatar.md @@ -0,0 +1,147 @@ +## Avatar 头像 + +用图标、图片或者字符的形式展示用户或事物信息。 + +### 基本用法 + +通过 `shape` 和 `size` 设置头像的形状和大小。 + +:::demo +```html + + + +``` +::: + +### 展示类型 + +支持三种类型:图标、图片和字符 + +:::demo +```html + +``` +::: + +### 图片加载失败的 fallback 行为 + +当展示类型为图片的时候,图片加载失败的 fallback 行为 + +:::demo +```html + + + +``` +::: + +### 图片如何适应容器框 + +当展示类型为图片的时候,使用 `fit` 属性定义图片如何适应容器框,同原生 [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)。 + +:::demo +```html + + + +``` +::: + +### Attributes + +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +| ----------------- | -------------------------------- | --------------- | ------ | ------ | +| icon | 设置头像的图标类型,参考 Icon 组件 | string | | | +| size | 设置头像的大小 | number/string | number / large / medium / small | large | +| shape | 设置头像的形状 | string | circle / square | circle | +| src | 图片头像的资源地址 | string | | | +| srcSet | 以逗号分隔的一个或多个字符串列表表明一系列用户代理使用的可能的图像 | string | | | +| alt | 描述图像的替换文本 | string | | | +| fit | 当展示类型为图片的时候,设置图片如何适应容器框 | string | fill / contain / cover / none / scale-down | cover | + + +### Events + +| 事件名 | 说明 | 回调参数 | +| ------ | ------------------ | -------- | +| error | 图片类头像加载失败的回调, 返回 false 会关闭组件默认的 fallback 行为 |(e: Event) | + +### Slot + +| 名称 | 说明 | +| ------ | ------------------ | +| default | 自定义头像展示内容 | diff --git a/examples/nav.config.json b/examples/nav.config.json index 21cd5c814d..36e3d3ac85 100644 --- a/examples/nav.config.json +++ b/examples/nav.config.json @@ -172,6 +172,10 @@ { "path": "/badge", "title": "Badge 标记" + }, + { + "path": "/avatar", + "title": "Avatar 头像" } ] }, @@ -565,6 +569,10 @@ { "path": "/infiniteScroll", "title": "InfiniteScroll" + }, + { + "path": "/avatar", + "title": "Avatar" } ] } @@ -851,6 +859,10 @@ { "path": "/infiniteScroll", "title": "InfiniteScroll" + }, + { + "path": "/avatar", + "title": "Avatar" } ] } @@ -1137,6 +1149,10 @@ { "path": "/infiniteScroll", "title": "InfiniteScroll" + }, + { + "path": "/avatar", + "title": "Avatar" } ] } diff --git a/packages/avatar/index.js b/packages/avatar/index.js new file mode 100644 index 0000000000..36589e34d8 --- /dev/null +++ b/packages/avatar/index.js @@ -0,0 +1,8 @@ +import Avatar from './src/main'; + +/* istanbul ignore next */ +Avatar.install = function(Vue) { + Vue.component(Avatar.name, Avatar); +}; + +export default Avatar; diff --git a/packages/avatar/src/main.vue b/packages/avatar/src/main.vue new file mode 100644 index 0000000000..3c306287b4 --- /dev/null +++ b/packages/avatar/src/main.vue @@ -0,0 +1,107 @@ + diff --git a/packages/theme-chalk/src/avatar.scss b/packages/theme-chalk/src/avatar.scss new file mode 100644 index 0000000000..e9b682b645 --- /dev/null +++ b/packages/theme-chalk/src/avatar.scss @@ -0,0 +1,49 @@ +@import "mixins/mixins"; +@import "common/var"; + +@include b(avatar) { + display: inline-block; + box-sizing: border-box; + text-align: center; + overflow: hidden; + color: $--avatar-font-color; + background: $--avatar-background-color; + width: $--avatar-size; + height: $--avatar-size; + line-height: $--avatar-size; + + >img { + width: 100%; + height: 100%; + } + + @include m(circle) { + border-radius: 50%; + } + + @include m(square) { + border-radius: 4px; + } + + @include m(icon) { + font-size: 18px; + } + + @include m(large) { + width: $--avatar-large-size; + height: $--avatar-large-size; + line-height: $--avatar-large-size; + } + + @include m(medium) { + width: $--avatar-medium-size; + height: $--avatar-medium-size; + line-height: $--avatar-medium-size; + } + + @include m(small) { + width: $--avatar-small-size; + height: $--avatar-small-size; + line-height: $--avatar-small-size; + } +} diff --git a/packages/theme-chalk/src/common/var.scss b/packages/theme-chalk/src/common/var.scss index 38550c03e2..c9597101ae 100644 --- a/packages/theme-chalk/src/common/var.scss +++ b/packages/theme-chalk/src/common/var.scss @@ -939,6 +939,17 @@ $--calendar-border: $--table-border !default; $--calendar-selected-background-color: #F2F8FE !default; $--calendar-cell-width: 85px !default; +/* Avatar +--------------------------*/ +/// color||Color|0 +$--avatar-font-color: #fff; +/// color||Color|0 +$--avatar-background-color: #C0C4CC; +$--avatar-size: 40px; +$--avatar-large-size: $--avatar-size; +$--avatar-medium-size: 36px; +$--avatar-small-size: 28px; + /* Break-point --------------------------*/ $--sm: 768px !default; diff --git a/packages/theme-chalk/src/index.scss b/packages/theme-chalk/src/index.scss index 6ba2f6c8e3..73e2505d3a 100644 --- a/packages/theme-chalk/src/index.scss +++ b/packages/theme-chalk/src/index.scss @@ -75,3 +75,4 @@ @import "./infiniteScroll.scss"; @import "./page-header.scss"; @import "./cascader-panel.scss"; +@import "./avatar.scss"; diff --git a/src/index.js b/src/index.js index e9f9bf2b9a..8f2ebae2ad 100644 --- a/src/index.js +++ b/src/index.js @@ -79,6 +79,7 @@ import Backtop from '../packages/backtop/index.js'; import InfiniteScroll from '../packages/infiniteScroll/index.js'; import PageHeader from '../packages/page-header/index.js'; import CascaderPanel from '../packages/cascader-panel/index.js'; +import Avatar from '../packages/avatar/index.js'; import locale from 'element-ui/src/locale'; import CollapseTransition from 'element-ui/src/transitions/collapse-transition'; @@ -157,6 +158,7 @@ const components = [ Backtop, PageHeader, CascaderPanel, + Avatar, CollapseTransition ]; @@ -275,5 +277,6 @@ export default { Backtop, InfiniteScroll, PageHeader, - CascaderPanel + CascaderPanel, + Avatar }; diff --git a/test/unit/specs/avatar.spec.js b/test/unit/specs/avatar.spec.js new file mode 100644 index 0000000000..aabcd42276 --- /dev/null +++ b/test/unit/specs/avatar.spec.js @@ -0,0 +1,123 @@ +import {createTest, createVue, destroyVM} from '../util'; +import Avatar from 'packages/avatar'; + +describe('Avatar', () => { + let vm; + afterEach(() => { + destroyVM(vm); + }); + + it('create', () => { + vm = createTest(Avatar); + expect(vm.$el).to.exist; + }); + + it('size is number', () => { + vm = createVue({ + template: ` + + + ` + }, true); + const avatarElm = vm.$el; + expect(avatarElm.style.height).to.equal('50px'); + }); + + it('size is string', () => { + vm = createVue({ + template: ` + + user + + ` + }, true); + const avatarElm = vm.$el; + expect(avatarElm.classList.contains('el-avatar--small')).to.be.true; + }); + + it('shape', () => { + vm = createVue({ + template: ` + + user + + ` + }, true); + const avatarElm = vm.$el; + expect(avatarElm.classList.contains('el-avatar--square')).to.be.true; + }); + + it('icon avatar', () => { + vm = createVue({ + template: ` + + + ` + }, true); + const avatarElm = vm.$el; + const iconELm = avatarElm.children[0]; + expect(avatarElm.classList.contains('el-avatar--icon')).to.be.true; + expect(iconELm.classList.contains('el-icon-user-solid')).to.be.true; + }); + + it('image avatar', () => { + vm = createVue({ + template: ` + + ` + }, true); + const imgElm = vm.$el.children[0]; + expect(imgElm.tagName.toUpperCase()).to.equal('IMG'); + expect(imgElm.src).to.equal('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'); + }); + + it('image fallback', (done) => { + vm = createVue({ + template: ` + + fallback + + `, + methods: { + errorHandler() { + return true; + } + } + }, true); + setTimeout(() => { + const avatarElm = vm.$el; + expect(avatarElm.textContent.trim()).to.equal('fallback'); + done(); + }, 3000); + }); + + it('image fit', (done) => { + vm = createVue({ + template: ` +
+ + +
+ + `, + data() { + return { + fits: ['fill', 'contain', 'cover', 'none', 'scale-down'], + url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg' + }; + } + }, true); + setTimeout(() => { + const containerElm = vm.$el; + expect(containerElm.children[0].children[0].style.objectFit).to.equal('cover'); + expect(containerElm.children[1].children[0].style.objectFit).to.equal('fill'); + expect(containerElm.children[2].children[0].style.objectFit).to.equal('contain'); + expect(containerElm.children[3].children[0].style.objectFit).to.equal('cover'); + expect(containerElm.children[4].children[0].style.objectFit).to.equal('none'); + expect(containerElm.children[5].children[0].style.objectFit).to.equal('scale-down'); + + done(); + }, 3000); + }); +}); + diff --git a/types/avatar.d.ts b/types/avatar.d.ts new file mode 100644 index 0000000000..4002e0e310 --- /dev/null +++ b/types/avatar.d.ts @@ -0,0 +1,20 @@ +import { ElementUIComponent } from './component' + +/** Avatar Component */ +export declare class ElAvatar extends ElementUIComponent { + icon: string; + + size: string | number; + + shape: string; + + src: string; + + error: () => false; + + srcSet: string; + + alt: string; + + fit: string; +} diff --git a/types/element-ui.d.ts b/types/element-ui.d.ts index 072ae91026..9dbe2ef7a7 100644 --- a/types/element-ui.d.ts +++ b/types/element-ui.d.ts @@ -77,6 +77,7 @@ import { ElImage } from './image' import { ElBacktop } from './backtop' import { ElInfiniteScroll } from './infiniteScroll' import { ElPageHeader } from './page-header' +import { ElAvatar } from './avatar' export interface InstallationOptions { locale: any, @@ -332,3 +333,6 @@ export const InfiniteScroll: PluginObject; /** PageHeader Component */ export class PageHeader extends ElPageHeader {} + +/** Avatar Component */ +export class Avatar extends ElAvatar {}