feat(core): add avatar components

This commit is contained in:
HerringtonDarkholme 2020-08-02 18:27:46 +08:00 committed by zazzaz
parent f096a8a65b
commit 685cad4edb
16 changed files with 315 additions and 12 deletions

View File

@ -39,6 +39,19 @@ module.exports = {
singleline: 3,
multiline: 1,
}],
'@typescript-eslint/member-delimiter-style': [
2,
{
multiline: {
delimiter: 'none',
requireLast: false,
},
singleline: {
delimiter: 'semi',
requireLast: true,
},
},
],
'vue/html-closing-bracket-spacing': 'error',
},
}

3
.storybook/demo.css Normal file
View File

@ -0,0 +1,3 @@
body {
font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
}

View File

@ -1,7 +1,8 @@
import { addDecorator } from '@storybook/html';
import { createApp } from 'vue';
import '../src/style/element-ui@2.13.2.css';
import { addDecorator } from '@storybook/html'
import { createApp } from 'vue'
import '../src/style/element-ui@2.13.2.css'
import install from '../packages/element-plus'
import './demo.css'
/**
* Wraps a story into a Vue Element
@ -11,11 +12,11 @@ import install from '../packages/element-plus'
const Wrapper = (template) => {
return {
data() {
return {};
return {}
},
template,
};
};
}
}
/**
* Custom Addon for previewing ElementPlus component in Vue3
@ -25,15 +26,15 @@ const Wrapper = (template) => {
* @return {HTMLElement}
*/
function CustomDecorator(content, context) {
const templateOrComponent = content();
const templateOrComponent = content()
const app = typeof templateOrComponent === 'string'
? createApp(Wrapper(templateOrComponent))
: createApp(templateOrComponent)
install(app)
const entry = document.createElement('div');
entry.className = 'element-plus-previewer';
app.mount(entry);
return entry;
const entry = document.createElement('div')
entry.className = 'element-plus-previewer'
app.mount(entry)
return entry
}
addDecorator(CustomDecorator);

12
lerna-debug.log Normal file
View File

@ -0,0 +1,12 @@
0 silly argv {
0 silly argv _: [ 'add' ],
0 silly argv scope: '@element-plus/avatar',
0 silly argv globs: [],
0 silly argv lernaVersion: '3.22.1',
0 silly argv '$0': '/usr/local/bin/lerna',
0 silly argv pkg: '@element-plus/utils'
0 silly argv }
1 notice cli v3.22.1
2 verbose rootPath /Users/bytedance/Desktop/code/element-plus
3 error Error: 404 Not Found - GET https://registry.npmjs.org/@element-plus%2futils - Not found
3 error at /Users/bytedance/Desktop/code/element-plus/node_modules/@evocateur/npm-registry-fetch/check-response.js:104:15

View File

@ -0,0 +1,15 @@
import { mount } from '@vue/test-utils'
import Avatar from '../src/index.vue'
const AXIOM = 'Rem is the best girl'
describe('Avatar.vue', () => {
test('render test', () => {
const wrapper = mount(Avatar, {
slots: {
default: AXIOM,
},
})
expect(wrapper.text()).toEqual(AXIOM)
})
})

View File

@ -0,0 +1,57 @@
<template>
<el-row class="demo-avatar demo-basic">
<el-col :span="12">
<div class="sub-title">circle</div>
<div class="demo-basic--circle">
<div class="block"><el-avatar :size="50" :src="circleUrl"></el-avatar></div>
<div class="block" v-for="size in sizeList" :key="size">
<el-avatar :size="size" :src="circleUrl"></el-avatar>
</div>
</div>
</el-col>
<el-col :span="12">
<div class="sub-title">square</div>
<div class="demo-basic--circle">
<div class="block"><el-avatar shape="square" :size="50" :src="squareUrl"></el-avatar></div>
<div class="block" v-for="size in sizeList" :key="size">
<el-avatar shape="square" :size="size" :src="squareUrl"></el-avatar>
</div>
</div>
</el-col>
</el-row>
</template>
<script lang="ts">
export default {
data () {
return {
circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
squareUrl: "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png",
sizeList: ["large", "medium", "small"]
}
}
}
</script>
<style scoped>
.demo-avatar .sub-title {
margin-bottom: 10px;
font-size: 14px;
color: #8492a6;
text-align: center;
}
.demo-avatar.demo-basic .demo-basic--circle,
.demo-avatar.demo-basic .demo-basic--square {
display: flex;
justify-content: space-between;
align-items: center;
}
.demo-avatar.demo-basic .demo-basic--circle .block:not(:last-child),
.demo-avatar.demo-basic .demo-basic--square .block:not(:last-child) {
flex: 1;
border-right: 1px solid rgba(224,230,237,.5);
text-align: center;
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<div class="demo-fit">
<div class="block" v-for="fit in fits" :key="fit">
<span class="title">{{ fit }}</span>
<el-avatar shape="square" :size="100" :fit="fit" :src="url"></el-avatar>
</div>
</div>
</template>
<script lang="ts">
export default {
data() {
return {
fits: ['fill', 'contain', 'cover', 'none', 'scale-down'],
url: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg'
}
}
}
</script>
<style>
.demo-fit {
display:flex;
text-align:center;
justify-content:space-between
}
.demo-fit .block {
flex:1;
display:flex;
flex-direction:column;
flex-grow:0
}
.demo-fit .title {
margin-bottom:10px;
font-size:14px;
color:#8492a6
}
</style>

View File

@ -0,0 +1,27 @@
export default {
title: 'Avatar',
}
export {default as BasicUsage} from './basic.vue'
export {default as FitAvatarContainer} from './fit-container.vue'
export const differentAvatarTypes = () => `
<div class="demo-type">
<div>
<el-avatar icon="el-icon-user-solid"></el-avatar>
</div>
<div>
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
</div>
<div>
<el-avatar> user </el-avatar>
</div>
</div>
`
export const fallbackWhenImageLoadsError = () => `
<div class="demo-type">
<el-avatar :size="60" src="https://empty">
<img src="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"/>
</el-avatar>
</div>
`

5
packages/avatar/index.ts Normal file
View File

@ -0,0 +1,5 @@
import { App } from 'vue'
import Avatar from './src/index.vue'
export default (app: App): void => {
app.component(Avatar.name, Avatar)
}

View File

@ -0,0 +1,15 @@
{
"name": "@element-plus/avatar",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.1"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.0"
},
"dependencies": {
"@element-plus/utils": "^0.0.0"
}
}

View File

@ -0,0 +1,85 @@
<template>
<span :class="avatarClass" :style="sizeStyle">
<img
v-if="(src || srcSet) && !hasLoadError"
:src="src"
@error="handleError"
:alt="alt"
:srcset="srcSet"
:style="{objectFit: fit}"
/>
<i v-else-if="icon" :class="icon"/>
<slot v-else/>
</span>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue'
import type {EventEmitter} from '@element-plus/utils/types'
export default defineComponent({
name: 'ElAvatar',
props: {
size: {
type: [Number, String],
validator(val) {
if (typeof val === 'string') {
return ['large', 'medium', 'small'].includes(val)
}
return typeof val === 'number'
}
},
shape: {
type: String,
default: 'circle',
validator(val: string) {
return ['circle', 'square'].includes(val)
}
},
icon: String,
src: String,
alt: String,
srcSet: String,
error: Function,
fit: {
type: String,
default: 'cover'
}
},
setup(props: any, {emit}) {
const hasLoadError = ref(false)
const avatarClass = computed(() => {
const {size, icon, shape} = props
let classList = ['el-avatar']
if (size && typeof size === 'string') {
classList.push(`el-avatar--${size}`)
}
if (icon) {
classList.push('el-avatar--icon')
}
if (shape) {
classList.push(`el-avatar--${shape}`)
}
return classList
})
const sizeStyle = computed(() => {
const {size} = props
return typeof size === 'number' ? {
height: `${size}px`,
width: `${size}px`,
lineHeight: `${size}px`
} : {}
})
function handleError(e: Event) {
hasLoadError.value = true
emit('error', e)
}
return {
hasLoadError, avatarClass, sizeStyle, handleError
}
}
})
</script>

View File

@ -49,8 +49,10 @@ interface IButtonProps {
circle: boolean;
}
type EmitFn = (evt: Event) => void
interface IButtonSetups {
_elFormItemSize: string;
_elFormItemSize: number;
buttonSize: string;
buttonDisabled: boolean;
handleClick: EmitFn;

View File

@ -1,4 +1,5 @@
import type { App } from 'vue'
import ElAvatar from '@element-plus/avatar'
import ElButton from '@element-plus/button'
import ElBadge from '@element-plus/badge'
import ElCard from '@element-plus/card'
@ -9,6 +10,7 @@ import ElTimeLine from '@element-plus/time-line'
import ElProgress from '@element-plus/progress'
export {
ElAvatar,
ElLayout,
ElButton,
ElBadge,
@ -20,6 +22,7 @@ export {
}
export default function install(app: App): void {
ElAvatar(app)
ElButton(app)
ElBadge(app)
ElCard(app)

View File

@ -14,6 +14,7 @@
"url": "https://github.com/element-plus/element-plus/issues"
},
"dependencies": {
"@element-plus/avatar": "^0.0.0",
"@element-plus/badge": "^0.0.0",
"@element-plus/button": "^0.0.0",
"@element-plus/layout": "^0.0.0",

View File

@ -0,0 +1,11 @@
{
"name": "@element-plus/utils",
"version": "0.0.0",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.0-rc.1"
},
"devDependencies": {
"@vue/test-utils": "^2.0.0-beta.0"
}
}

16
packages/utils/types.ts Normal file
View File

@ -0,0 +1,16 @@
type OptionalKeys<T extends object> = {
[K in keyof T]: T extends Record<K, T[K]>
? never
: K
}[keyof T]
type RequiredKeys<T extends object> = Exclude<keyof T, OptionalKeys<T>>
type MonoArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg?: T[K]) => void
type BiArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg: T[K]) => void
export type EventEmitter<T extends object> =
MonoArgEmitter<T, OptionalKeys<T>> & BiArgEmitter<T, RequiredKeys<T>>
type A = Required<{}>