feat: Improve sidebar UI, add emoji icons instead of ionicons (#1605)

Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Nithin David Thomas 2021-01-12 14:45:10 +05:30 committed by GitHub
parent 346830ab1d
commit 764c90174e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 195 additions and 52 deletions

View file

@ -74,12 +74,10 @@ export default {
} }
.conversation--details { .conversation--details {
border-top: 1px solid $color-border-light; padding: 0 var(--space-normal);
padding: var(--space-normal);
} }
.contact-conversation--panel { .contact-conversation--panel {
border-top: 1px solid $color-border-light;
height: 100%; height: 100%;
} }

View file

@ -3,11 +3,14 @@
<contact-details-item <contact-details-item
:title="$t('CONTACT_PANEL.CONVERSATIONS.TITLE')" :title="$t('CONTACT_PANEL.CONVERSATIONS.TITLE')"
icon="ion-chatboxes" icon="ion-chatboxes"
emoji="💬"
/> />
<div v-if="!uiFlags.isFetching"> <div v-if="!uiFlags.isFetching" class="contact-conversation__wrap">
<p v-if="!previousConversations.length" class="no-results"> <div v-if="!previousConversations.length" class="no-label-message">
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }} <span>
</p> {{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
</span>
</div>
<div v-else class="contact-conversation--list"> <div v-else class="contact-conversation--list">
<conversation-card <conversation-card
v-for="conversation in previousConversations" v-for="conversation in previousConversations"
@ -78,11 +81,18 @@ export default {
@import '~dashboard/assets/scss/mixins'; @import '~dashboard/assets/scss/mixins';
.contact-conversation--panel { .contact-conversation--panel {
padding: $space-normal; padding: 0 var(--space-slab) var(--space-two);
} }
.no-results { .contact-conversation__wrap {
margin: 0; margin-left: var(--space-medium);
color: $color-gray; }
.no-label-message {
color: var(--b-500);
}
.conv-details--item {
padding-bottom: 0;
} }
</style> </style>

View file

@ -3,6 +3,7 @@
<contact-details-item <contact-details-item
:title="$t('CONTACT_PANEL.CUSTOM_ATTRIBUTES.TITLE')" :title="$t('CONTACT_PANEL.CUSTOM_ATTRIBUTES.TITLE')"
icon="ion-code" icon="ion-code"
emoji="📕"
/> />
<div <div
v-for="attribute in listOfAttributes" v-for="attribute in listOfAttributes"
@ -45,12 +46,15 @@ export default {
<style scoped> <style scoped>
.custom-attributes--panel { .custom-attributes--panel {
border-top: 1px solid var(--b-100); padding: 0 var(--space-slab) var(--space-slab);
padding: var(--space-normal);
} }
.conv-details--item {
padding-bottom: 0;
}
.custom-attribute--row { .custom-attribute--row {
margin-bottom: var(--space-small); margin-bottom: var(--space-small);
margin-left: var(--space-medium);
} }
.custom-attribute--row__attribute { .custom-attribute--row__attribute {

View file

@ -1,11 +1,15 @@
<template> <template>
<div class="conv-details--item"> <div class="conv-details--item">
<h4 class="conv-details--item__label"> <h4 class="conv-details--item__label text-block-title">
<div> <div class="title--icon">
<i v-if="icon" :class="icon" class="conv-details--item__icon"></i> <emoji-or-icon :icon="icon" :emoji="emoji" />
{{ title }} {{ title }}
</div> </div>
<button v-if="showEdit" @click="onEdit"> <button
v-if="showEdit"
class="button clear small edit-button"
@click="onEdit"
>
{{ $t('CONTACT_PANEL.EDIT_LABEL') }} {{ $t('CONTACT_PANEL.EDIT_LABEL') }}
</button> </button>
</h4> </h4>
@ -18,10 +22,16 @@
</template> </template>
<script> <script>
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
export default { export default {
components: {
EmojiOrIcon,
},
props: { props: {
title: { type: String, required: true }, title: { type: String, required: true },
icon: { type: String, default: '' }, icon: { type: String, default: '' },
emoji: { type: String, default: '' },
value: { type: [String, Number], default: '' }, value: { type: [String, Number], default: '' },
showEdit: { type: Boolean, default: false }, showEdit: { type: Boolean, default: false },
}, },
@ -38,29 +48,32 @@ export default {
@import '~dashboard/assets/scss/mixins'; @import '~dashboard/assets/scss/mixins';
.conv-details--item { .conv-details--item {
padding-bottom: var(--space-slab); padding-bottom: var(--space-normal);
&:last-child {
padding-bottom: 0;
}
.conv-details--item__label { .conv-details--item__label {
align-items: center; align-items: center;
display: flex; display: flex;
font-size: $font-size-small;
font-weight: $font-weight-medium;
justify-content: space-between; justify-content: space-between;
margin-bottom: $space-micro; margin-bottom: var(--space-smaller);
button { .edit-button {
cursor: pointer; padding: 0;
color: $color-body;
} }
} }
.conv-details--item__value { .conv-details--item__value {
word-break: break-all; word-break: break-all;
margin-top: $space-small; margin-left: var(--space-medium);
}
.title--icon .icon--emoji,
.title--icon .icon--font {
margin-right: var(--space-small);
}
.title--icon {
display: flex;
align-items: center;
} }
} }
</style> </style>

View file

@ -10,30 +10,35 @@
:title="$t('EDIT_CONTACT.FORM.LOCATION.LABEL')" :title="$t('EDIT_CONTACT.FORM.LOCATION.LABEL')"
:value="location" :value="location"
icon="ion-map" icon="ion-map"
emoji="📍"
/> />
<contact-details-item <contact-details-item
v-if="ipAddress" v-if="ipAddress"
:title="$t('CONTACT_PANEL.IP_ADDRESS')" :title="$t('CONTACT_PANEL.IP_ADDRESS')"
:value="ipAddress" :value="ipAddress"
icon="ion-android-locate" icon="ion-android-locate"
emoji="🧭"
/> />
<contact-details-item <contact-details-item
v-if="browser.browser_name" v-if="browser.browser_name"
:title="$t('CONTACT_PANEL.BROWSER')" :title="$t('CONTACT_PANEL.BROWSER')"
:value="browserName" :value="browserName"
icon="ion-ios-world-outline" icon="ion-ios-world-outline"
emoji="🌐"
/> />
<contact-details-item <contact-details-item
v-if="browser.platform_name" v-if="browser.platform_name"
:title="$t('CONTACT_PANEL.OS')" :title="$t('CONTACT_PANEL.OS')"
:value="platformName" :value="platformName"
icon="ion-laptop" icon="ion-laptop"
emoji="💻"
/> />
<contact-details-item <contact-details-item
v-if="referer" v-if="referer"
:title="$t('CONTACT_PANEL.INITIATED_FROM')" :title="$t('CONTACT_PANEL.INITIATED_FROM')"
:value="referer" :value="referer"
icon="ion-link" icon="ion-link"
emoji="🔗"
> >
<a :href="referer" rel="noopener noreferrer nofollow" target="_blank"> <a :href="referer" rel="noopener noreferrer nofollow" target="_blank">
{{ referer }} {{ referer }}
@ -44,6 +49,7 @@
:title="$t('CONTACT_PANEL.INITIATED_AT')" :title="$t('CONTACT_PANEL.INITIATED_AT')"
:value="initiatedAt.timestamp" :value="initiatedAt.timestamp"
icon="ion-clock" icon="ion-clock"
emoji="🕰"
/> />
</div> </div>
<contact-custom-attributes <contact-custom-attributes
@ -211,8 +217,7 @@ export default {
} }
.conversation--details { .conversation--details {
border-top: 1px solid $color-border-light; padding: 0 var(--space-slab);
padding: $space-normal;
} }
.conversation--labels { .conversation--labels {
@ -230,10 +235,6 @@ export default {
} }
} }
.contact-conversation--panel {
border-top: 1px solid $color-border-light;
}
.contact--mute { .contact--mute {
color: $alert-color; color: $alert-color;
display: block; display: block;

View file

@ -22,6 +22,7 @@
:href="contact.email ? `mailto:${contact.email}` : ''" :href="contact.email ? `mailto:${contact.email}` : ''"
:value="contact.email" :value="contact.email"
icon="ion-email" icon="ion-email"
emoji="✉️"
:title="$t('CONTACT_PANEL.EMAIL_ADDRESS')" :title="$t('CONTACT_PANEL.EMAIL_ADDRESS')"
show-copy show-copy
/> />
@ -30,24 +31,27 @@
:href="contact.phone_number ? `tel:${contact.phone_number}` : ''" :href="contact.phone_number ? `tel:${contact.phone_number}` : ''"
:value="contact.phone_number" :value="contact.phone_number"
icon="ion-ios-telephone" icon="ion-ios-telephone"
emoji="📞"
:title="$t('CONTACT_PANEL.PHONE_NUMBER')" :title="$t('CONTACT_PANEL.PHONE_NUMBER')"
/> />
<contact-info-row <contact-info-row
v-if="additionalAttributes.location" v-if="additionalAttributes.location"
:value="additionalAttributes.location" :value="additionalAttributes.location"
icon="ion-map" icon="ion-map"
emoji="🌍"
:title="$t('CONTACT_PANEL.LOCATION')" :title="$t('CONTACT_PANEL.LOCATION')"
/> />
<contact-info-row <contact-info-row
:value="additionalAttributes.company_name" :value="additionalAttributes.company_name"
icon="ion-briefcase" icon="ion-briefcase"
emoji="🏢"
:title="$t('CONTACT_PANEL.COMPANY')" :title="$t('CONTACT_PANEL.COMPANY')"
/> />
</div> </div>
</div> </div>
<woot-button <woot-button
class="expanded" class="clear edit-contact"
variant="hollow primary small" variant="primary small"
@click="toggleEditModal" @click="toggleEditModal"
> >
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }} {{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
@ -115,7 +119,7 @@ export default {
@import '~dashboard/assets/scss/mixins'; @import '~dashboard/assets/scss/mixins';
.contact--profile { .contact--profile {
align-items: flex-start; align-items: flex-start;
padding: $space-normal; padding: var(--space-normal) var(--space-normal) var(--space-large);
.user-thumbnail-box { .user-thumbnail-box {
margin-right: $space-normal; margin-right: $space-normal;
@ -151,7 +155,7 @@ export default {
} }
.contact--metadata { .contact--metadata {
margin: $space-small 0 $space-normal; margin: var(--space-normal) 0 0;
} }
.social--icons { .social--icons {
@ -159,4 +163,10 @@ export default {
font-size: $font-weight-normal; font-size: $font-weight-normal;
} }
} }
.edit-contact {
padding: 0 var(--space-slab);
margin-left: var(--space-slab);
margin-top: var(--space-smaller);
}
</style> </style>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="contact-info--row"> <div class="contact-info--row">
<a v-if="href" :href="href" class="contact-info--details"> <a v-if="href" :href="href" class="contact-info--details">
<i :class="icon" class="contact-info--icon" /> <emoji-or-icon :icon="icon" :emoji="emoji" />
<span v-if="value" class="text-truncate" :title="value">{{ value }}</span> <span v-if="value" class="text-truncate" :title="value">{{ value }}</span>
<span v-else class="text-muted">{{ <span v-else class="text-muted">{{
$t('CONTACT_PANEL.NOT_AVAILABLE') $t('CONTACT_PANEL.NOT_AVAILABLE')
@ -18,7 +18,7 @@
</a> </a>
<div v-else class="contact-info--details"> <div v-else class="contact-info--details">
<i :class="icon" class="contact-info--icon" /> <emoji-or-icon :icon="icon" :emoji="emoji" />
<span v-if="value" class="text-truncate">{{ value }}</span> <span v-if="value" class="text-truncate">{{ value }}</span>
<span v-else class="text-muted">{{ <span v-else class="text-muted">{{
$t('CONTACT_PANEL.NOT_AVAILABLE') $t('CONTACT_PANEL.NOT_AVAILABLE')
@ -29,7 +29,12 @@
<script> <script>
import copy from 'copy-text-to-clipboard'; import copy from 'copy-text-to-clipboard';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
export default { export default {
components: {
EmojiOrIcon,
},
mixins: [alertMixin], mixins: [alertMixin],
props: { props: {
href: { href: {
@ -40,6 +45,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
emoji: {
type: String,
required: true,
},
value: { value: {
type: String, type: String,
default: '', default: '',
@ -71,7 +80,7 @@ export default {
.contact-info--details { .contact-info--details {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: $space-smaller; margin-bottom: var(--space-one);
color: $color-body; color: $color-body;
.copy-icon { .copy-icon {
@ -87,4 +96,9 @@ export default {
} }
} }
} }
.contact-info--details .icon--emoji,
.contact-info--details .icon--font {
margin-right: var(--space-small);
}
</style> </style>

View file

@ -7,18 +7,21 @@
<contact-details-item <contact-details-item
:title="$t('CONTACT_PANEL.LABELS.TITLE')" :title="$t('CONTACT_PANEL.LABELS.TITLE')"
icon="ion-pricetags" icon="ion-pricetags"
emoji="🏷️"
:show-edit="true" :show-edit="true"
@edit="onEdit" @edit="onEdit"
/> />
<woot-label <div class="label-wrap">
v-for="label in activeLabels" <woot-label
:key="label.id" v-for="label in activeLabels"
:title="label.title" :key="label.id"
:description="label.description" :title="label.title"
:bg-color="label.color" :description="label.description"
/> :bg-color="label.color"
<div v-if="!activeLabels.length"> />
{{ $t('CONTACT_PANEL.LABELS.NO_AVAILABLE_LABELS') }} <div v-if="!activeLabels.length" class="no-label-message">
<span>{{ $t('CONTACT_PANEL.LABELS.NO_AVAILABLE_LABELS') }}</span>
</div>
</div> </div>
<add-label-to-conversation <add-label-to-conversation
v-if="isEditing" v-if="isEditing"
@ -119,15 +122,24 @@ export default {
@import '~dashboard/assets/scss/mixins'; @import '~dashboard/assets/scss/mixins';
.contact-conversation--panel { .contact-conversation--panel {
padding: $space-normal; padding: var(--space-medium) var(--space-slab) var(--space-two);
} }
.contact-conversation--list .conv-details--item {
padding-bottom: 0;
}
.conversation--label { .conversation--label {
color: $color-white; color: $color-white;
margin-right: $space-small; margin-right: $space-small;
font-size: $font-size-small; font-size: $font-size-small;
padding: $space-smaller; padding: $space-smaller;
} }
.label-wrap {
margin-left: var(--space-medium);
}
.no-label-message {
color: var(--b-500);
}
.select-tags { .select-tags {
.multiselect { .multiselect {

View file

@ -0,0 +1,47 @@
<template>
<i v-if="showWrap" :class="className">{{ iconContent }}</i>
</template>
<script>
import { hasEmojiSupport } from 'shared/helpers/emoji';
import { mapGetters } from 'vuex';
export default {
props: {
icon: { type: String, default: '' },
emoji: { type: String, default: '' },
},
computed: {
...mapGetters({ uiSettings: 'getUISettings' }),
isIconTypeEmoji() {
const { icon_type: iconType } = this.uiSettings;
return iconType === 'emoji';
},
showEmoji() {
return this.isIconTypeEmoji && this.emoji && hasEmojiSupport(this.emoji);
},
showIcon() {
return !this.showEmoji && this.icon;
},
showWrap() {
return this.showEmoji || this.showIcon;
},
iconContent() {
return this.showEmoji ? this.emoji : '';
},
className() {
return {
'icon--emoji': this.showEmoji,
'icon--font': this.showIcon,
[this.icon]: this.showIcon,
};
},
},
};
</script>
<style lang="scss" scoped>
.icon--emoji {
font-style: normal;
}
</style>

View file

@ -0,0 +1,34 @@
/**
* Detects support for emoji character sets.
*
* Based on the Modernizr emoji detection.
* https://github.com/Modernizr/Modernizr/blob/347ddb078116cee91b25b6e897e211b023f9dcb4/feature-detects/emoji.js
*
* @return {Boolean} true or false
* @example
*
* hasEmojiSupport()
* // => true|false
*/
export const hasEmojiSupport = () => {
const pixelRatio = window.devicePixelRatio || 1;
const offset = 12 * pixelRatio;
const node = document.createElement('canvas');
// canvastext support
if (
!node.getContext ||
!node.getContext('2d') ||
typeof node.getContext('2d').fillText !== 'function'
) {
return false;
}
const ctx = node.getContext('2d');
ctx.fillStyle = '#f00';
ctx.textBaseline = 'top';
ctx.font = '32px Arial';
ctx.fillText('\ud83d\udc28', 0, 0); // U+1F428 KOALA
return ctx.getImageData(offset, offset, 1, 1).data[0] !== 0;
};