feat: Improve sidebar UI, add emoji icons instead of ionicons (#1605)
Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
parent
346830ab1d
commit
764c90174e
10 changed files with 195 additions and 52 deletions
|
@ -74,12 +74,10 @@ export default {
|
|||
}
|
||||
|
||||
.conversation--details {
|
||||
border-top: 1px solid $color-border-light;
|
||||
padding: var(--space-normal);
|
||||
padding: 0 var(--space-normal);
|
||||
}
|
||||
|
||||
.contact-conversation--panel {
|
||||
border-top: 1px solid $color-border-light;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
<contact-details-item
|
||||
:title="$t('CONTACT_PANEL.CONVERSATIONS.TITLE')"
|
||||
icon="ion-chatboxes"
|
||||
emoji="💬"
|
||||
/>
|
||||
<div v-if="!uiFlags.isFetching">
|
||||
<p v-if="!previousConversations.length" class="no-results">
|
||||
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
|
||||
</p>
|
||||
<div v-if="!uiFlags.isFetching" class="contact-conversation__wrap">
|
||||
<div v-if="!previousConversations.length" class="no-label-message">
|
||||
<span>
|
||||
{{ $t('CONTACT_PANEL.CONVERSATIONS.NO_RECORDS_FOUND') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="contact-conversation--list">
|
||||
<conversation-card
|
||||
v-for="conversation in previousConversations"
|
||||
|
@ -78,11 +81,18 @@ export default {
|
|||
@import '~dashboard/assets/scss/mixins';
|
||||
|
||||
.contact-conversation--panel {
|
||||
padding: $space-normal;
|
||||
padding: 0 var(--space-slab) var(--space-two);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
margin: 0;
|
||||
color: $color-gray;
|
||||
.contact-conversation__wrap {
|
||||
margin-left: var(--space-medium);
|
||||
}
|
||||
|
||||
.no-label-message {
|
||||
color: var(--b-500);
|
||||
}
|
||||
|
||||
.conv-details--item {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<contact-details-item
|
||||
:title="$t('CONTACT_PANEL.CUSTOM_ATTRIBUTES.TITLE')"
|
||||
icon="ion-code"
|
||||
emoji="📕"
|
||||
/>
|
||||
<div
|
||||
v-for="attribute in listOfAttributes"
|
||||
|
@ -45,12 +46,15 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
.custom-attributes--panel {
|
||||
border-top: 1px solid var(--b-100);
|
||||
padding: var(--space-normal);
|
||||
padding: 0 var(--space-slab) var(--space-slab);
|
||||
}
|
||||
|
||||
.conv-details--item {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.custom-attribute--row {
|
||||
margin-bottom: var(--space-small);
|
||||
margin-left: var(--space-medium);
|
||||
}
|
||||
|
||||
.custom-attribute--row__attribute {
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<div class="conv-details--item">
|
||||
<h4 class="conv-details--item__label">
|
||||
<div>
|
||||
<i v-if="icon" :class="icon" class="conv-details--item__icon"></i>
|
||||
<h4 class="conv-details--item__label text-block-title">
|
||||
<div class="title--icon">
|
||||
<emoji-or-icon :icon="icon" :emoji="emoji" />
|
||||
{{ title }}
|
||||
</div>
|
||||
<button v-if="showEdit" @click="onEdit">
|
||||
<button
|
||||
v-if="showEdit"
|
||||
class="button clear small edit-button"
|
||||
@click="onEdit"
|
||||
>
|
||||
{{ $t('CONTACT_PANEL.EDIT_LABEL') }}
|
||||
</button>
|
||||
</h4>
|
||||
|
@ -18,10 +22,16 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmojiOrIcon,
|
||||
},
|
||||
props: {
|
||||
title: { type: String, required: true },
|
||||
icon: { type: String, default: '' },
|
||||
emoji: { type: String, default: '' },
|
||||
value: { type: [String, Number], default: '' },
|
||||
showEdit: { type: Boolean, default: false },
|
||||
},
|
||||
|
@ -38,29 +48,32 @@ export default {
|
|||
@import '~dashboard/assets/scss/mixins';
|
||||
|
||||
.conv-details--item {
|
||||
padding-bottom: var(--space-slab);
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
padding-bottom: var(--space-normal);
|
||||
|
||||
.conv-details--item__label {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
justify-content: space-between;
|
||||
margin-bottom: $space-micro;
|
||||
margin-bottom: var(--space-smaller);
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
color: $color-body;
|
||||
.edit-button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.conv-details--item__value {
|
||||
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>
|
||||
|
|
|
@ -10,30 +10,35 @@
|
|||
:title="$t('EDIT_CONTACT.FORM.LOCATION.LABEL')"
|
||||
:value="location"
|
||||
icon="ion-map"
|
||||
emoji="📍"
|
||||
/>
|
||||
<contact-details-item
|
||||
v-if="ipAddress"
|
||||
:title="$t('CONTACT_PANEL.IP_ADDRESS')"
|
||||
:value="ipAddress"
|
||||
icon="ion-android-locate"
|
||||
emoji="🧭"
|
||||
/>
|
||||
<contact-details-item
|
||||
v-if="browser.browser_name"
|
||||
:title="$t('CONTACT_PANEL.BROWSER')"
|
||||
:value="browserName"
|
||||
icon="ion-ios-world-outline"
|
||||
emoji="🌐"
|
||||
/>
|
||||
<contact-details-item
|
||||
v-if="browser.platform_name"
|
||||
:title="$t('CONTACT_PANEL.OS')"
|
||||
:value="platformName"
|
||||
icon="ion-laptop"
|
||||
emoji="💻"
|
||||
/>
|
||||
<contact-details-item
|
||||
v-if="referer"
|
||||
:title="$t('CONTACT_PANEL.INITIATED_FROM')"
|
||||
:value="referer"
|
||||
icon="ion-link"
|
||||
emoji="🔗"
|
||||
>
|
||||
<a :href="referer" rel="noopener noreferrer nofollow" target="_blank">
|
||||
{{ referer }}
|
||||
|
@ -44,6 +49,7 @@
|
|||
:title="$t('CONTACT_PANEL.INITIATED_AT')"
|
||||
:value="initiatedAt.timestamp"
|
||||
icon="ion-clock"
|
||||
emoji="🕰"
|
||||
/>
|
||||
</div>
|
||||
<contact-custom-attributes
|
||||
|
@ -211,8 +217,7 @@ export default {
|
|||
}
|
||||
|
||||
.conversation--details {
|
||||
border-top: 1px solid $color-border-light;
|
||||
padding: $space-normal;
|
||||
padding: 0 var(--space-slab);
|
||||
}
|
||||
|
||||
.conversation--labels {
|
||||
|
@ -230,10 +235,6 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.contact-conversation--panel {
|
||||
border-top: 1px solid $color-border-light;
|
||||
}
|
||||
|
||||
.contact--mute {
|
||||
color: $alert-color;
|
||||
display: block;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
:href="contact.email ? `mailto:${contact.email}` : ''"
|
||||
:value="contact.email"
|
||||
icon="ion-email"
|
||||
emoji="✉️"
|
||||
:title="$t('CONTACT_PANEL.EMAIL_ADDRESS')"
|
||||
show-copy
|
||||
/>
|
||||
|
@ -30,24 +31,27 @@
|
|||
:href="contact.phone_number ? `tel:${contact.phone_number}` : ''"
|
||||
:value="contact.phone_number"
|
||||
icon="ion-ios-telephone"
|
||||
emoji="📞"
|
||||
:title="$t('CONTACT_PANEL.PHONE_NUMBER')"
|
||||
/>
|
||||
<contact-info-row
|
||||
v-if="additionalAttributes.location"
|
||||
:value="additionalAttributes.location"
|
||||
icon="ion-map"
|
||||
emoji="🌍"
|
||||
:title="$t('CONTACT_PANEL.LOCATION')"
|
||||
/>
|
||||
<contact-info-row
|
||||
:value="additionalAttributes.company_name"
|
||||
icon="ion-briefcase"
|
||||
emoji="🏢"
|
||||
:title="$t('CONTACT_PANEL.COMPANY')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
class="expanded"
|
||||
variant="hollow primary small"
|
||||
class="clear edit-contact"
|
||||
variant="primary small"
|
||||
@click="toggleEditModal"
|
||||
>
|
||||
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
||||
|
@ -115,7 +119,7 @@ export default {
|
|||
@import '~dashboard/assets/scss/mixins';
|
||||
.contact--profile {
|
||||
align-items: flex-start;
|
||||
padding: $space-normal;
|
||||
padding: var(--space-normal) var(--space-normal) var(--space-large);
|
||||
|
||||
.user-thumbnail-box {
|
||||
margin-right: $space-normal;
|
||||
|
@ -151,7 +155,7 @@ export default {
|
|||
}
|
||||
|
||||
.contact--metadata {
|
||||
margin: $space-small 0 $space-normal;
|
||||
margin: var(--space-normal) 0 0;
|
||||
}
|
||||
|
||||
.social--icons {
|
||||
|
@ -159,4 +163,10 @@ export default {
|
|||
font-size: $font-weight-normal;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-contact {
|
||||
padding: 0 var(--space-slab);
|
||||
margin-left: var(--space-slab);
|
||||
margin-top: var(--space-smaller);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="contact-info--row">
|
||||
<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-else class="text-muted">{{
|
||||
$t('CONTACT_PANEL.NOT_AVAILABLE')
|
||||
|
@ -18,7 +18,7 @@
|
|||
</a>
|
||||
|
||||
<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-else class="text-muted">{{
|
||||
$t('CONTACT_PANEL.NOT_AVAILABLE')
|
||||
|
@ -29,7 +29,12 @@
|
|||
<script>
|
||||
import copy from 'copy-text-to-clipboard';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmojiOrIcon,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
href: {
|
||||
|
@ -40,6 +45,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emoji: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -71,7 +80,7 @@ export default {
|
|||
.contact-info--details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $space-smaller;
|
||||
margin-bottom: var(--space-one);
|
||||
color: $color-body;
|
||||
|
||||
.copy-icon {
|
||||
|
@ -87,4 +96,9 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact-info--details .icon--emoji,
|
||||
.contact-info--details .icon--font {
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,18 +7,21 @@
|
|||
<contact-details-item
|
||||
:title="$t('CONTACT_PANEL.LABELS.TITLE')"
|
||||
icon="ion-pricetags"
|
||||
emoji="🏷️"
|
||||
:show-edit="true"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
<woot-label
|
||||
v-for="label in activeLabels"
|
||||
:key="label.id"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:bg-color="label.color"
|
||||
/>
|
||||
<div v-if="!activeLabels.length">
|
||||
{{ $t('CONTACT_PANEL.LABELS.NO_AVAILABLE_LABELS') }}
|
||||
<div class="label-wrap">
|
||||
<woot-label
|
||||
v-for="label in activeLabels"
|
||||
:key="label.id"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:bg-color="label.color"
|
||||
/>
|
||||
<div v-if="!activeLabels.length" class="no-label-message">
|
||||
<span>{{ $t('CONTACT_PANEL.LABELS.NO_AVAILABLE_LABELS') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<add-label-to-conversation
|
||||
v-if="isEditing"
|
||||
|
@ -119,15 +122,24 @@ export default {
|
|||
@import '~dashboard/assets/scss/mixins';
|
||||
|
||||
.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 {
|
||||
color: $color-white;
|
||||
margin-right: $space-small;
|
||||
font-size: $font-size-small;
|
||||
padding: $space-smaller;
|
||||
}
|
||||
.label-wrap {
|
||||
margin-left: var(--space-medium);
|
||||
}
|
||||
.no-label-message {
|
||||
color: var(--b-500);
|
||||
}
|
||||
|
||||
.select-tags {
|
||||
.multiselect {
|
||||
|
|
47
app/javascript/shared/components/EmojiOrIcon.vue
Normal file
47
app/javascript/shared/components/EmojiOrIcon.vue
Normal 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>
|
34
app/javascript/shared/helpers/emoji.js
Normal file
34
app/javascript/shared/helpers/emoji.js
Normal 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;
|
||||
};
|
Loading…
Reference in a new issue