mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-27 02:01:15 +08:00
feat(core): add avatar components
This commit is contained in:
parent
f096a8a65b
commit
685cad4edb
13
.eslintrc.js
13
.eslintrc.js
@ -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
3
.storybook/demo.css
Normal file
@ -0,0 +1,3 @@
|
||||
body {
|
||||
font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
|
||||
}
|
@ -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
12
lerna-debug.log
Normal 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
|
15
packages/avatar/__tests__/avatar.spec.ts
Normal file
15
packages/avatar/__tests__/avatar.spec.ts
Normal 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)
|
||||
})
|
||||
})
|
57
packages/avatar/doc/basic.vue
Normal file
57
packages/avatar/doc/basic.vue
Normal 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>
|
37
packages/avatar/doc/fit-container.vue
Normal file
37
packages/avatar/doc/fit-container.vue
Normal 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>
|
27
packages/avatar/doc/index.stories.ts
Normal file
27
packages/avatar/doc/index.stories.ts
Normal 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
5
packages/avatar/index.ts
Normal 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)
|
||||
}
|
15
packages/avatar/package.json
Normal file
15
packages/avatar/package.json
Normal 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"
|
||||
}
|
||||
}
|
85
packages/avatar/src/index.vue
Normal file
85
packages/avatar/src/index.vue
Normal 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>
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
11
packages/utils/package.json
Normal file
11
packages/utils/package.json
Normal 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
16
packages/utils/types.ts
Normal 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<{}>
|
Loading…
Reference in New Issue
Block a user