ChatMessage: Added a new component for chat messages

pull/21412/head
Ivan Monastyrsky 2021-10-21 23:33:56 +07:00
parent 5390f4069e
commit f4f0712ae3
16 changed files with 809 additions and 4 deletions

View File

@ -86,5 +86,6 @@
"empty": "./packages/empty/index.js",
"descriptions": "./packages/descriptions/index.js",
"descriptions-item": "./packages/descriptions-item/index.js",
"result": "./packages/result/index.js"
"result": "./packages/result/index.js",
"chat-message": "./packages/chat-message/index.js"
}

View File

@ -0,0 +1,223 @@
## ChatMessage
Message component for creating full-fledged chats
### Basic usage
:::demo Use `type` and `size` to define style and size
```html
<el-row :gutter="10">
<el-chat-message type="primary" size="large" text="Message large size and primary style"/>
<el-chat-message text="Default message size and style"/>
<el-chat-message type="success" size="medium" text="Medium size message and style success"/>
<el-chat-message type="warning" size="small" text="Small size message and warning style"/>
<el-chat-message type="danger" size="mini" text="Mini size and danger style message"/>
</el-row>
```
:::
### Positioning
:::demo Use `position` to define the side of the correspondence
```html
<el-chat-message text="Hi! How are you?"/>
<el-chat-message type="primary" position="right" text="That's great! How are you?"/>
```
:::
### Personalization
:::demo Use `name` and `avatar` to personalize the message
```html
<el-chat-message
name="Taylor Otwell"
avatar="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
text="Hi, when is Vue 4 coming out?"
/>
<el-chat-message
name="Evan You"
avatar="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
text="Hi Taylor! When pigs fly"
type="primary"
position="right"
/>
```
:::
### Status and time stamp
You can add one of the statuses `sending`, `delivered`, `read` and time to the message
:::demo Use `status` and` stamp` to set time and status
```html
<el-chat-message
name="Taylor Otwell"
avatar="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
text="Hi, when is Vue 4 coming out?"
stamp="11:36"
/>
<el-chat-message
name="Evan You"
avatar="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
text="Hi Taylor! When pigs fly"
stamp="11:36"
status="read"
type="primary"
position="right"
/>
<el-chat-message
name="Evan You"
avatar="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
text="I wanted to ask, when will Laravel 13 pro max come out?"
stamp="11:36"
status="delivered"
type="primary"
position="right"
/>
<el-chat-message
name="Evan You"
avatar="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
text="Taylor? Answer!"
status="sending"
type="primary"
position="right"
/>
```
:::
### Message Stacks
Consecutive messages from the same person can be displayed in a grouped stack
:::demo Now we pass an array of messages into `text`
```html
<template>
<el-chat-message
name="Taylor Otwell"
avatar="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
text="Hi, when is Vue 4 coming out?"
stamp="11:36"
/>
<el-chat-message
name="Evan You"
avatar="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
:text="messages"
type="primary"
position="right"
/>
</template>
<script>
export default {
data() {
return {
messages: [
{
text: "Hi Taylor! When pigs fly",
stamp: "11:36",
status: "read"
},
{
text: "I wanted to ask, when will Laravel 13 pro max come out?",
stamp: "11:36",
status: "delivered"
},
{
text: "Taylor? Answer!",
status: "sending"
}
]
};
}
};
</script>
```
:::
### Slots
Using slots in message elements
:::demo
```html
<template>
<el-chat-message stamp="11:36" text="Hi!">
<template slot="avatar">
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
</template>
<template slot="name">
<el-link>Taylor Otwell</el-link>
</template>
<template slot="text" slot-scope="{ message }">
{{ message.text }} When will Vue 4 be released?
</template>
<template slot="stamp">
11:36
</template>
</el-chat-message>
<el-chat-message
name="Evan You"
avatar="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
:text="messages"
type="primary"
position="right"
>
<template slot="text" slot-scope="{ message, $index }">
<strong>{{ $index }}</strong>: <i>{{ message.text }}</i>
</template>
</el-chat-message>
</template>
<script>
export default {
data() {
return {
messages: [
{
text: "Hi Taylor! When pigs fly",
stamp: "11:36",
status: "read"
},
{
text: "I wanted to ask, when will Laravel 13 pro max come out?",
stamp: "11:36",
status: "delivered"
},
{
text: "Taylor? Answer!",
status: "sending"
}
]
};
}
};
</script>
```
:::
### Attributes
| Attribute | Description | Type | Accepted values | Default |
|-----------|--------------------------------- |--------------- |-----------------------------------------|-------- |
| size | Message size | string | large / default / medium / small / mini | default |
| type | Message type | string | primary / success / warning / danger | — |
| text | Message or stack of messages | string / array | — | — |
| name | Username | string | — | — |
| avatar | Avatar url | string | — | — |
| stamp | Time stamp | string | — | — |
| status | Message status | string | none / sending / delivered / read | none |
| position | Side of the message box | string | left / right | left |
### Scoped Slot
| Name | Description |
|---------- |--------------------------------------------------------------- |
| name | Username. The scope parameter is { row: { name }, $index } |
| avatar | User avatar. The scope parameter is { row: { avatar }, $index } |
| text | Message Text. The scope parameter is { message, $index } |
| stamp | Time stamp. The scope parameter is { message, $index } |
| status | Message status. The scope parameter is { message, $index } |

View File

@ -0,0 +1 @@
## ChatMessage

View File

@ -0,0 +1 @@
## ChatMessage

View File

@ -0,0 +1 @@
## ChatMessage chat-message

View File

@ -307,6 +307,10 @@
{
"path": "/drawer",
"title": "Drawer 抽屉"
},
{
"path": "/chat-message",
"title": "ChatMessage"
}
]
}
@ -621,6 +625,10 @@
{
"path": "/drawer",
"title": "Drawer"
},
{
"path": "/chat-message",
"title": "ChatMessage"
}
]
}
@ -935,6 +943,10 @@
{
"path": "/drawer",
"title": "Drawer"
},
{
"path": "/chat-message",
"title": "ChatMessage"
}
]
}
@ -1249,6 +1261,10 @@
{
"path": "/drawer",
"title": "Drawer"
},
{
"path": "/chat-message",
"title": "ChatMessage"
}
]
}

View File

@ -0,0 +1,8 @@
import ChatMessage from './src/chat-message';
/* istanbul ignore next */
ChatMessage.install = function(Vue) {
Vue.component(ChatMessage.name, ChatMessage);
};
export default ChatMessage;

View File

@ -0,0 +1,111 @@
<template>
<div
@click="handleClick"
:class="[
'el-chat-message', `is-${position}`,
'el-chat-message--' + messageSize,
type ? 'el-chat-message--' + type : ''
]"
>
<div class="el-chat-message__list">
<div v-for="(message, index) in stackMessages" :key="`message-${index}`" class="el-chat-message__box">
<slot name="avatar" v-bind:row="{ avatar }" v-bind:$index="index">
<div class="el-chat-message__avatar" v-if="avatar">
<el-avatar v-if="index === 0" :src="avatar"/>
</div>
</slot>
<div class="el-chat-message__body">
<div class="el-chat-message__name" v-if="index === 0">
<slot name="name" v-bind:row="{ name }" v-bind:$index="index">
{{ name }}
</slot>
</div>
<slot name="text" v-bind:message="message" v-bind:$index="index">
{{ message.text }}
</slot>
<div class="el-chat-message__footer">
<slot name="stamp" v-bind:message="message" v-bind:$index="index">
<div class="stamp" v-if="message.stamp">
{{ message.stamp }}
</div>
</slot>
<slot name="status" v-bind:message="message" v-bind:$index="index">
<div class="status" v-if="message.status !== 'none'">
<i class="el-icon-loading" v-if="message.status === 'sending'"></i>
<i class="el-icon-check" v-if="message.status === 'delivered'"></i>
<div class="el-chat-message__check-icon" v-if="message.status === 'read'">
<i class="el-icon-check"></i>
<i class="el-icon-check second"></i>
</div>
</div>
</slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ElChatMessage',
props: {
name: String,
stamp: String,
text: Array | String,
avatar: String,
size: {
type: String,
default: 'default',
validator: (value) => {
return ['default', 'large', 'medium', 'small', 'mini'].indexOf(value) !== -1;
}
},
position: {
type: String,
default: 'left',
validator: (value) => {
return ['left', 'right'].indexOf(value) !== -1;
}
},
status: {
type: String,
default: 'none',
validator: (value) => {
return ['none', 'sending', 'delivered', 'read'].indexOf(value) !== -1;
}
},
type: {
type: String,
validator: (value) => {
return ['primary', 'success', 'warning', 'danger'].indexOf(value) !== -1;
}
}
},
computed: {
messageSize() {
return this.size || (this.$ELEMENT || {}).size;
},
stackMessages() {
if (Array.isArray(this.text)) {
return this.text;
}
return [{
text: this.text,
stamp: this.stamp,
status: this.status
}];
}
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>

View File

@ -0,0 +1,143 @@
@import "mixins/mixins";
@import "mixins/chat-message";
@import "common/var";
@include b(chat-message) {
@include e(list) {
display: flex;
flex-direction: column;
@include e(box) {
display: flex;
align-items: flex-end;
@include e(body) {
color: $--chat-message--color;
background-color: $--color-info-light;
@include e(name) {
margin-bottom: 2px;
font-weight: bold;
}
@include e(footer) {
float: right;
display: flex;
justify-content: flex-end;
color: darken($--color-info-light, 20);
@include e(check-icon){
color: #11c711;
position: relative;
.el-icon-check {
font-weight: bold;
}
}
}
}
}
}
@include when(left) {
@include e(list) {
align-items: flex-start;
}
@include e(name) {
text-align: left;
}
}
@include when(right) {
@include e(list) {
align-items: flex-end;
@include e(box) {
flex-direction: row-reverse;
}
}
@include e(name) {
text-align: right;
}
}
@include m('primary') {
@include chat-message-variant($--chat-message-primary-font-color, $--chat-message-primary-background-color)
}
@include m('success') {
@include chat-message-variant($--chat-message-success-font-color, $--chat-message-success-background-color)
}
@include m('warning') {
@include chat-message-variant($--chat-message-warning-font-color, $--chat-message-warning-background-color)
}
@include m('danger') {
@include chat-message-variant($--chat-message-danger-font-color, $--chat-message-danger-background-color)
}
@include m('default') {
@include chat-message-size(
$--chat-message-avatar-size,
$--chat-message-margin-gutter,
$--chat-message-font-size,
$--chat-message-font-size-footer,
$--chat-message-padding-vertical,
$--chat-message-padding-horizontal,
$--chat-message-border-radius
)
}
@include m('large') {
@include chat-message-size(
$--chat-message-large-avatar-size,
$--chat-message-large-margin-gutter,
$--chat-message-large-font-size,
$--chat-message-large-font-size-footer,
$--chat-message-large-padding-vertical,
$--chat-message-large-padding-horizontal,
$--chat-message-large-border-radius
)
}
@include m('medium') {
@include chat-message-size(
$--chat-message-medium-avatar-size,
$--chat-message-medium-margin-gutter,
$--chat-message-medium-font-size,
$--chat-message-medium-font-size-footer,
$--chat-message-medium-padding-vertical,
$--chat-message-medium-padding-horizontal,
$--chat-message-medium-border-radius
)
}
@include m('small') {
@include chat-message-size(
$--chat-message-small-avatar-size,
$--chat-message-small-margin-gutter,
$--chat-message-small-font-size,
$--chat-message-small-font-size-footer,
$--chat-message-small-padding-vertical,
$--chat-message-small-padding-horizontal,
$--chat-message-small-border-radius
)
}
@include m('mini') {
@include chat-message-size(
$--chat-message-mini-avatar-size,
$--chat-message-mini-margin-gutter,
$--chat-message-mini-font-size,
$--chat-message-mini-font-size-footer,
$--chat-message-mini-padding-vertical,
$--chat-message-mini-padding-horizontal,
$--chat-message-mini-border-radius
)
}
}

View File

@ -147,6 +147,97 @@ $--disabled-border-base: $--border-color-light !default;
$--icon-color: #666 !default;
$--icon-color-base: $--color-info !default;
/* ChatMessage
-------------------------- */
$--chat-message--color: $--color-black !default;
$--icon-color-base: $--color-info !default;
$--chat-message-avatar-size: 40px;
$--chat-message-margin-gutter: 10px;
$--chat-message-font-size-footer: 12px;
/// fontSize||Font|1
$--chat-message-font-size: $--font-size-base !default;
/// borderRadius||Border|2
$--chat-message-border-radius: 20px;
/// padding||Spacing|3
$--chat-message-padding-vertical: 12px !default;
/// padding||Spacing|3
$--chat-message-padding-horizontal: 20px !default;
$--chat-message-large-avatar-size: 50px;
$--chat-message-large-margin-gutter: 10px;
$--chat-message-large-font-size-footer: 13px;
/// fontSize||Font|1
$--chat-message-large-font-size: 15px !default;
/// borderRadius||Border|2
$--chat-message-large-border-radius: 20px;
/// padding||Spacing|3
$--chat-message-large-padding-vertical: 14px !default;
/// padding||Spacing|3
$--chat-message-large-padding-horizontal: 20px !default;
$--chat-message-medium-avatar-size: 36px;
$--chat-message-medium-margin-gutter: 10px;
$--chat-message-medium-font-size-footer: 11px;
/// fontSize||Font|1
$--chat-message-medium-font-size: 13px !default;
/// borderRadius||Border|2
$--chat-message-medium-border-radius: 20px;
/// padding||Spacing|3
$--chat-message-medium-padding-vertical: 6px !default;
/// padding||Spacing|3
$--chat-message-medium-padding-horizontal: 14px !default;
$--chat-message-small-avatar-size: 30px;
$--chat-message-small-margin-gutter: 8px;
$--chat-message-small-font-size-footer: 11px;
/// fontSize||Font|1
$--chat-message-small-font-size: 13px !default;
/// borderRadius||Border|2
$--chat-message-small-border-radius: 20px;
/// padding||Spacing|3
$--chat-message-small-padding-vertical: 6px !default;
/// padding||Spacing|3
$--chat-message-small-padding-horizontal: 14px !default;
$--chat-message-mini-avatar-size: 26px;
$--chat-message-mini-margin-gutter: 6px;
$--chat-message-mini-font-size-footer: 11px;
/// fontSize||Font|1
$--chat-message-mini-font-size: 12px !default;
/// borderRadius||Border|2
$--chat-message-mini-border-radius: 20px;
/// padding||Spacing|3
$--chat-message-mini-padding-vertical: 6px !default;
/// padding||Spacing|3
$--chat-message-mini-padding-horizontal: 10px !default;
/// color||Color|0
$--chat-message-primary-font-color: $--color-white !default;
/// color||Color|0
$--chat-message-primary-background-color: $--color-primary !default;
/// color||Color|0
$--chat-message-success-font-color: $--color-white !default;
/// color||Color|0
$--chat-message-success-background-color: $--color-success !default;
/// color||Color|0
$--chat-message-warning-font-color: $--color-white !default;
/// color||Color|0
$--chat-message-warning-background-color: $--color-warning !default;
/// color||Color|0
$--chat-message-danger-font-color: $--color-white !default;
/// color||Color|0
$--chat-message-danger-background-color: $--color-danger !default;
$--chat-message-hover-tint-percent: 20% !default;
$--chat-message-active-shade-percent: 10% !default;
/* Checkbox
-------------------------- */
/// fontSize||Font|1

View File

@ -84,3 +84,4 @@
@import "./descriptions.scss";
@import "./descriptions-item.scss";
@import "./result.scss";
@import "./chat-message.scss";

View File

@ -0,0 +1,106 @@
@import "../common/var";
@import "mixins";
@mixin chat-message-variant($color, $background-color) {
@include e(list) {
@include e(box) {
@include e(body) {
color: $color;
background-color: $background-color;
@include e(footer) {
color: mix($--color-white, $background-color, 70);
@include e(check-icon){
color: mix($--color-white, $background-color, 70);
}
}
}
}
}
}
@mixin chat-message-size(
$avatar-size,
$margin-gutter,
$font-size,
$font-size-footer,
$padding-vertical,
$padding-horizontal,
$border-radius
) {
@include e(list) {
@include e(box) {
margin-bottom: $margin-gutter;
@include e(avatar) {
width: $avatar-size;
margin-right: $margin-gutter;
.el-avatar {
width: $avatar-size;
height: $avatar-size;
}
}
@include e(body) {
padding: $padding-vertical $padding-horizontal;
border-radius: $border-radius;
font-size: $font-size;
@include e(name) {
margin-bottom: 2px;
font-size: $font-size;
font-weight: bold;
}
@include e(footer) {
font-size: $font-size-footer;
margin-left: 10px;
line-height: $font-size + 2;
@include pseudo(last-child) {
@include e(check-icon) {
.el-icon-check.second {
position: absolute;
left: 4px;
top: $font-size - $font-size-footer;
}
}
}
}
}
}
}
@include when(left) {
@include e(list) {
@include e(box) {
@include e(avatar) {
margin-left: $margin-gutter;
}
@include pseudo(first-child) {
@include e(body) {
border-radius: $border-radius $border-radius $border-radius 5px;
}
}
}
}
}
@include when(right) {
@include e(list) {
@include e(box) {
@include e(avatar) {
margin-left: $margin-gutter;
}
@include pseudo(first-child) {
@include e(body) {
border-radius: $border-radius $border-radius 5px $border-radius;
}
}
}
}
}
}

View File

@ -88,6 +88,7 @@ import Empty from '../packages/empty/index.js';
import Descriptions from '../packages/descriptions/index.js';
import DescriptionsItem from '../packages/descriptions-item/index.js';
import Result from '../packages/result/index.js';
import ChatMessage from '../packages/chat-message/index.js';
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';
@ -175,6 +176,7 @@ const components = [
Descriptions,
DescriptionsItem,
Result,
ChatMessage,
CollapseTransition
];
@ -302,5 +304,6 @@ export default {
Empty,
Descriptions,
DescriptionsItem,
Result
Result,
ChatMessage
};

View File

@ -0,0 +1,51 @@
import { createTest, destroyVM } from '../util';
import ChatMessage from 'packages/chat-message';
const testProps = {
text: 'Test text',
name: 'Evan You'
};
describe('ChatMessage', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
it('create', () => {
vm = createTest(ChatMessage, {
...testProps,
type: 'primary'
}, true);
let messageElm = vm.$el;
expect(messageElm.classList.contains('el-chat-message--primary')).to.be.true;
});
it('size medium', () => {
vm = createTest(ChatMessage, {
...testProps,
size: 'medium'
}, true);
let messageElm = vm.$el;
expect(messageElm.classList.contains('el-chat-message--medium')).to.be.true;
});
it('type primary', () => {
vm = createTest(ChatMessage, {
...testProps,
type: 'primary'
}, true);
let messageElm = vm.$el;
expect(messageElm.classList.contains('el-chat-message--primary')).to.be.true;
});
it('position right', () => {
vm = createTest(ChatMessage, {
...testProps,
position: 'right'
}, true);
let messageElm = vm.$el;
expect(messageElm.classList.contains('is-right')).to.be.true;
});
});

44
types/chat-message.d.ts vendored Normal file
View File

@ -0,0 +1,44 @@
import {ElementUIComponent, ElementUIComponentSize} from './component'
/** ChatMessage type */
export type ChatMessageType = 'primary' | 'success' | 'warning' | 'danger'
/** ChatMessage status */
export type ChatMessageStatus = 'none' | 'sending' | 'delivered' | 'read'
/** ChatMessage position */
export type ChatMessagePosition = 'left' | 'right'
/** ChatMessage text */
export interface ChatMessageText {
text: string,
stamp?: string,
status?: ChatMessageStatus
}
/** ChatMessage Component */
export declare class ElChatMessage extends ElementUIComponent {
/** ChatMessage size */
size: ElementUIComponentSize
/** ChatMessage status */
status: ChatMessageStatus
/** ChatMessage type */
type: ChatMessageType
/** ChatMessage position */
position: ChatMessagePosition
/** Sender name */
name: string
/** Datetime message */
stamp: string
/** Array of message */
text: Array<ChatMessageText> | string
/** Sender avatar */
avatar: string
}

View File

@ -88,6 +88,7 @@ import { ElSpinner } from './spinner'
import { ElDescriptions } from './descriptions'
import { ElDescriptionsItem } from './descriptions-item'
import { ElResult } from './result'
import { ElChatMessage } from './chat-message'
export interface InstallationOptions {
locale: any,
@ -376,3 +377,6 @@ export class DescriptionsItem extends ElDescriptionsItem {}
/** Result Component */
export class Result extends ElResult {}
/** ChatMessage Component */
export class ChatMessage extends ElChatMessage {}