Compare commits

...

41 commits

Author SHA1 Message Date
Nithin David Thomas
3452a07935
Merge branch 'develop' into chore/chat-list-design 2022-07-01 12:09:44 +05:30
Nithin David
bc14f6d542 Fixes broken styles 2022-06-21 18:07:07 +05:30
Nithin David
52eb4c3183 Merge branch 'develop' of https://github.com/chatwoot/chatwoot into chore/chat-list-design 2022-06-20 16:19:02 +05:30
Nithin David
8e81ef5951 Changes to conversation card 2022-03-30 20:59:39 +05:30
Sivin Varghese
8a57a53e3d
Merge branch 'develop' into design/update-label-theme 2022-03-30 19:02:41 +05:30
Nithin David
b570f3db22 Improves contrast of light colors 2022-03-30 18:48:57 +05:30
Sivin Varghese
4906d5b080
Merge branch 'develop' into design/update-label-theme 2022-03-30 18:11:12 +05:30
Nithin David
7e7f6631b3 Fixes code climate issue 2022-03-29 20:39:18 +05:30
Sivin Varghese
e1d8f32e94
Merge branch 'develop' into design/update-label-theme 2022-03-29 20:17:30 +05:30
Sivin Varghese
5755a3804e
Merge branch 'develop' into design/update-label-theme 2022-03-29 18:21:40 +05:30
Nithin David
fc12580b3e Changes for contact page 2022-03-29 17:01:36 +05:30
Nithin David
08f76206cf enhancement: Changes theme of labels to smooth 2022-03-29 16:52:58 +05:30
Nithin David Thomas
7e8de04448
Merge branch 'develop' into chore/chat-list-design 2022-03-25 11:38:25 +05:30
Pranav Raj S
15950ac8c4
Merge branch 'develop' into chore/chat-list-design 2022-03-21 13:07:23 +05:30
Nithin David Thomas
7c3fcbba2e
Merge branch 'develop' into chore/chat-list-design 2022-03-17 19:07:14 +05:30
Nithin David
12b6a42aa1 Show labels on chat list 2022-03-16 15:32:26 +05:30
Muhsin Keloth
6012b90c78
Merge branch 'develop' into chore/chat-list-design 2022-02-08 14:39:39 +05:30
Muhsin Keloth
f1c61cf306
Merge branch 'develop' into chore/chat-list-design 2022-01-20 20:33:20 +05:30
Nithin David Thomas
00b9803959
Merge branch 'develop' into chore/chat-list-design 2022-01-20 10:45:20 +05:30
Nithin David Thomas
463ac25051
Merge branch 'develop' into chore/chat-list-design 2022-01-14 06:15:42 +05:30
Nithin David
f84bcbfc7c Style changes to card 2022-01-11 11:52:20 +05:30
Nithin David
b900e38022 Improves conversation card design 2022-01-07 16:16:49 +05:30
Muhsin Keloth
1e9fa55bba
Merge branch 'develop' into chore/chat-list-design 2022-01-05 13:19:25 +05:30
Nithin David
8abf171409 Fixes test cases 2022-01-05 13:15:15 +05:30
Nithin David Thomas
50b70093e8
Merge branch 'develop' into chore/chat-list-design 2022-01-05 12:17:55 +05:30
Nithin David
39254be442 Adjusts icon only button size 2022-01-04 17:49:39 +05:30
Nithin David
9793a4d71c Style changes to suit new design 2022-01-03 17:21:04 +05:30
Nithin David
eb9d9ace96 Merge branch 'develop' of https://github.com/chatwoot/chatwoot into chore/chat-list-design 2021-12-30 12:11:30 +05:30
Nithin David
c17b532f95 Fixes issue with labels alignment 2021-12-21 15:03:45 +05:30
Nithin David
c327a7c20e Fixes right spacing for inbox name 2021-12-20 20:40:01 +05:30
Sivin Varghese
fee43711cc
Merge branch 'develop' into chore/chat-list-design 2021-12-20 10:14:37 +05:30
Nithin David
b0a0d08d8d Merge branch 'develop' of https://github.com/chatwoot/chatwoot into chore/chat-list-design 2021-12-15 13:27:58 +05:30
Nithin David
c316c53595 Style changes 2021-12-14 19:29:21 +05:30
Nithin David
b81affbbbf Fuxes inbox name design on chatlist 2021-12-14 18:58:14 +05:30
Nithin David Thomas
aadbc3e0f4
Merge branch 'develop' into chore/chat-list-design 2021-12-10 15:56:45 +05:30
Nithin David
e4f52fb183 Codeclimate fixes 2021-12-06 20:44:16 +05:30
Sivin Varghese
0f2f8a188b
Merge branch 'develop' into chore/chat-list-design 2021-12-06 08:52:29 +05:30
Nithin David
9977884a2c Fixes colors with labels 2021-12-03 21:16:45 +05:30
Sivin Varghese
4aab553567
Merge branch 'develop' into chore/chat-list-design 2021-12-03 21:10:48 +05:30
Muhsin Keloth
034701db8f
Merge branch 'develop' into chore/chat-list-design 2021-12-03 20:53:35 +05:30
Nithin David
2be97cf50a Chore: Updates design of conversation list 2021-12-03 20:46:49 +05:30
31 changed files with 572 additions and 340 deletions

View file

@ -56,10 +56,60 @@ code {
padding-right: var(--space-normal); padding-right: var(--space-normal);
} }
$badge-size: var(--space-normal);
$label-badge-size: var(--space-slab);
.badge { .badge {
border-radius: var(--border-radius-normal); border-radius: var(--border-radius-normal);
font-weight: var(--font-weight-bold);
height: min-content;
line-height: 1;
&.small {
font-size: var(--font-size-nano);
min-width: var(--space-slab);
padding: var(--space-micro);
}
&.rounded {
border-radius: var(--space-mega);
}
&.secondary {
background: var(--s-75);
color: var(--s-500);
font-weight: var(--font-weight-bold);
min-width: unset;
}
} }
.badge--label,
.badge--icon {
background: var(--s-100);
border-radius: var(--border-radius-small);
display: inline-flex;
margin-right: var(--space-smaller);
}
.badge--icon {
align-items: center;
height: $badge-size;
justify-content: center;
min-width: $badge-size;
.svg-icon {
color: inherit;
}
}
.badge--label {
height: $label-badge-size;
margin-left: var(--space-smaller);
min-width: $label-badge-size;
}
.padding-right-small { .padding-right-small {
padding-right: var(--space-one); padding-right: var(--space-one);
} }

View file

@ -220,10 +220,10 @@ $accordionmenu-arrow-size: 6px;
$badge-background: $primary-color; $badge-background: $primary-color;
$badge-color: $white; $badge-color: $white;
$badge-color-alt: $black; $badge-color-alt: $color-body;
$badge-palette: $foundation-palette; $badge-palette: $foundation-palette;
$badge-padding: var(--space-smaller); $badge-padding: var(--space-smaller);
$badge-minwidth: 2.1em; $badge-minwidth: var(--space-normal);
$badge-font-size: var(--font-size-nano); $badge-font-size: var(--font-size-nano);
// 10. Breadcrumbs // 10. Breadcrumbs

View file

@ -37,6 +37,10 @@ $default-button-height: 4.0rem;
border-radius: $space-larger; border-radius: $space-larger;
} }
&.not-rounded {
border-radius: 0;
}
// @TODO Use with link // @TODO Use with link
&.compact { &.compact {

View file

@ -11,138 +11,10 @@
} }
.conversation { .conversation {
@include flex;
@include flex-shrink;
@include padding(0 0 0 $space-normal);
border-bottom: 1px solid transparent;
border-left: $space-micro solid transparent;
border-top: 1px solid transparent;
cursor: pointer;
position: relative;
&.active { &.active {
animation: left-shift-animation .25s $swift-ease-out-function; animation: left-shift-animation .25s $swift-ease-out-function;
background: $color-background;
border-bottom-color: $color-border-light;
border-left-color: $color-woot;
border-top-color: $color-border-light;
.conversation--details {
border-top-color: transparent;
}
+.conversation .conversation--details {
border-top-color: transparent;
}
}
&:first-child {
.conversation--details {
border-top-color: transparent;
}
}
&:last-child {
.conversation--details {
border-bottom-color: $color-border-light;
}
} }
.conversation--details {
@include margin(0 0 0 $space-one);
@include border-light-bottom;
@include border-light-top;
@include padding($space-slab 0);
border-bottom-color: transparent;
}
.conversation--user {
font-size: $font-size-small;
margin-bottom: 0;
text-transform: capitalize;
.label {
left: $space-micro;
max-width: $space-jumbo;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
top: $space-micro;
white-space: nowrap;
}
}
.conversation--message {
color: $color-body;
font-size: $font-size-small;
font-weight: $font-weight-normal;
height: $space-medium;
line-height: $space-medium;
margin: 0;
max-width: 96%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 27rem;
}
.conversation--meta {
@include flex;
flex-direction: column;
position: absolute;
right: $space-normal;
top: $space-normal;
.unread {
$unread-size: $space-normal;
@include round-corner;
@include light-shadow;
background: darken($success-color, 3%);
color: $color-white;
display: none;
font-size: $font-size-micro;
font-weight: $font-weight-black;
height: $unread-size;
line-height: $unread-size;
margin-left: auto;
margin-top: $space-smaller;
min-width: $unread-size;
padding: 0 $space-smaller;
text-align: center;
}
.timestamp {
color: $dark-gray;
font-size: $font-size-micro;
font-weight: $font-weight-normal;
line-height: $space-normal;
margin-left: auto;
}
}
&.unread-chat {
.unread {
display: inline-block;
}
.conversation--message {
font-weight: $font-weight-bold;
}
.conversation--user {
font-weight: $font-weight-bold;
}
}
&.compact {
padding-left: 0;
.conversation--details {
border-radius: var(--border-radius-small);
margin-left: 0;
padding-left: var(--space-two);
padding-right: var(--space-small);
}
}
} }

View file

@ -86,11 +86,6 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.page-title {
margin-bottom: $zero;
margin-left: $space-normal;
}
.status--filter { .status--filter {
@include padding($zero null $zero $space-normal); @include padding($zero null $zero $space-normal);
@include margin($zero); @include margin($zero);

View file

@ -6,22 +6,7 @@
border-top-width: 0; border-top-width: 0;
} }
// Tab chat type
.tab--chat-type {
@include flex;
.tabs-title {
a {
font-size: $font-size-default;
font-weight: $font-weight-medium;
padding-bottom: $space-slab;
padding-top: $space-slab;
}
}
}
.tabs-title { .tabs-title {
@include margin($zero $space-slab);
.badge { .badge {
background: $color-background; background: $color-background;
@ -33,36 +18,19 @@
padding: $space-smaller; padding: $space-smaller;
} }
&:first-child { a,
margin-left: 0; .button {
}
&:last-child {
margin-right: 0;
}
&:hover,
&:focus {
a {
color: darken($medium-gray, 20%);
}
}
a {
@include position(relative, 1px null null null); @include position(relative, 1px null null null);
align-items: center;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
color: $medium-gray; height: var(--space-large);
display: flex;
flex-direction: row;
font-size: $font-size-small;
transition: border-color .15s $swift-ease-out-function; transition: border-color .15s $swift-ease-out-function;
} }
&.is-active { &.is-active {
a {
a,
.button {
border-bottom-color: $color-woot; border-bottom-color: $color-woot;
color: $color-woot;
} }
.badge { .badge {
@ -70,4 +38,26 @@
color: $color-woot; color: $color-woot;
} }
} }
// Sleek tabs
&.smooth {
a,
.button {
border-bottom: 0;
top: 0;
.button__content {
align-items: center;
display: inline-flex;
}
}
&.is-active {
.badge {
background: var(--s-100);
color: var(--s-600);
}
}
}
} }

View file

@ -5,7 +5,7 @@
class="chat-list__top" class="chat-list__top"
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }" :class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
> >
<h1 class="page-title text-truncate" :title="pageTitle"> <h1 class="page-sub-title text-truncate" :title="pageTitle">
{{ pageTitle }} {{ pageTitle }}
</h1> </h1>
@ -77,7 +77,6 @@
v-if="!hasAppliedFiltersOrActiveFolders" v-if="!hasAppliedFiltersOrActiveFolders"
:items="assigneeTabItems" :items="assigneeTabItems"
:active-tab="activeAssigneeTab" :active-tab="activeAssigneeTab"
class="tab--chat-type"
@chatTabChange="updateAssigneeTab" @chatTabChange="updateAssigneeTab"
/> />
@ -658,7 +657,7 @@ export default {
width: 35rem; width: 35rem;
} }
@include breakpoint(xxlarge up) { @include breakpoint(xxlarge up) {
width: 38rem; width: 36rem;
} }
@include breakpoint(xxxlarge up) { @include breakpoint(xxxlarge up) {
flex-basis: 46rem; flex-basis: 46rem;
@ -678,6 +677,9 @@ export default {
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
} }
.page-sub-title {
margin-left: var(--space-normal);
}
.delete-custom-view__button { .delete-custom-view__button {
margin-right: var(--space-normal); margin-right: var(--space-normal);
} }

View file

@ -85,9 +85,6 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$badge-size: var(--space-normal);
$label-badge-size: var(--space-slab);
.button { .button {
margin: var(--space-small) 0; margin: var(--space-small) 0;
} }
@ -127,6 +124,7 @@ $label-badge-size: var(--space-slab);
font-size: var(--font-size-nano); font-size: var(--font-size-nano);
} }
.badge--label, .badge--label,
.badge--icon { .badge--icon {
display: inline-flex; display: inline-flex;

View file

@ -171,7 +171,7 @@ export default {
} }
.secondary-menu--title { .secondary-menu--title {
color: var(--s-600); color: var(--s-700);
display: flex; display: flex;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
line-height: var(--space-two); line-height: var(--space-two);

View file

@ -8,7 +8,7 @@
:style="{ background: color }" :style="{ background: color }"
class="label-color-dot" class="label-color-dot"
/> />
<span v-if="!href">{{ title }}</span> <span v-if="!href" class="label__title">{{ title }}</span>
<a v-else :href="href" :style="anchorStyle">{{ title }}</a> <a v-else :href="href" :style="anchorStyle">{{ title }}</a>
<button <button
v-if="showClose" v-if="showClose"
@ -112,16 +112,16 @@ export default {
background: var(--s-50); background: var(--s-50);
color: var(--s-800); color: var(--s-800);
border: 1px solid var(--s-75); border: 1px solid var(--s-75);
height: var(--space-medium); height: var(--space-two);
line-height: 1.2;
&.small { &.small {
font-size: var(--font-size-micro); font-size: var(--font-size-micro);
padding: var(--space-micro) var(--space-smaller); padding: var(--space-micro) var(--space-smaller);
line-height: 1.2;
letter-spacing: 0.15px;
} }
.label--icon { .label--icon {
color: inherit;
cursor: pointer; cursor: pointer;
margin-right: var(--space-smaller); margin-right: var(--space-smaller);
} }
@ -141,16 +141,14 @@ export default {
/* Color Schemes */ /* Color Schemes */
&.primary { &.primary {
background: var(--w-100); background: var(--w-100);
color: var(--w-900); color: var(--w-700);
border: 1px solid var(--w-200);
a { a {
color: var(--w-900); color: var(--w-900);
} }
} }
&.secondary { &.secondary {
background: var(--s-100); background: var(--s-100);
color: var(--s-900); color: var(--s-700);
border: 1px solid var(--s-200);
a { a {
color: var(--s-900); color: var(--s-900);
} }
@ -158,7 +156,6 @@ export default {
&.success { &.success {
background: var(--g-100); background: var(--g-100);
color: var(--g-900); color: var(--g-900);
border: 1px solid var(--g-200);
a { a {
color: var(--g-900); color: var(--g-900);
} }
@ -166,7 +163,7 @@ export default {
&.alert { &.alert {
background: var(--r-100); background: var(--r-100);
color: var(--r-900); color: var(--r-900);
border: 1px solid var(--r-200);
a { a {
color: var(--r-900); color: var(--r-900);
} }
@ -174,7 +171,6 @@ export default {
&.warning { &.warning {
background: var(--y-100); background: var(--y-100);
color: var(--y-900); color: var(--y-900);
border: 1px solid var(--y-200);
a { a {
color: var(--y-900); color: var(--y-900);
} }
@ -198,7 +194,15 @@ export default {
} }
.label-action--button { .label-action--button {
margin-bottom: var(--space-minus-micro); color: inherit;
display: inline-flex;
align-items: center;
}
.label__title {
max-width: inherit;
overflow: hidden;
text-overflow: ellipsis;
} }
.label-color-dot { .label-color-dot {

View file

@ -3,14 +3,21 @@
:class="{ :class="{
'tabs-title': true, 'tabs-title': true,
'is-active': active, 'is-active': active,
[variant]: variant,
}" }"
> >
<a @click="onTabClick"> <woot-button
:size="size"
:variant="buttonVariant"
:color-scheme="colorScheme"
:is-rounded="isRounded"
@click="onTabClick"
>
{{ name }} {{ name }}
<span v-if="showBadge" class="badge"> <span v-if="showBadge" class="badge">
{{ getItemCount }} {{ getItemCount }}
</span> </span>
</a> </woot-button>
</li> </li>
</template> </template>
<script> <script>
@ -37,6 +44,15 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
variant: {
type: String,
default: '',
// Available variants: [ '', 'smooth']
},
size: {
type: String,
default: '',
},
}, },
computed: { computed: {
@ -47,6 +63,17 @@ export default {
getItemCount() { getItemCount() {
return this.count; return this.count;
}, },
colorScheme() {
if (this.variant === 'smooth') return 'secondary';
return this.active ? '' : 'secondary';
},
buttonVariant() {
if (this.variant === 'smooth') return this.active ? 'smooth' : 'clear';
return 'clear';
},
isRounded() {
return this.variant === 'smooth';
},
}, },
methods: { methods: {

View file

@ -60,6 +60,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isRounded: {
type: Boolean,
default: true,
},
}, },
computed: { computed: {
variantClasses() { variantClasses() {
@ -84,6 +88,7 @@ export default {
this.classNames, this.classNames,
this.isDisabled ? 'disabled' : '', this.isDisabled ? 'disabled' : '',
this.isExpanded ? 'expanded' : '', this.isExpanded ? 'expanded' : '',
this.isRounded ? '' : 'not-rounded',
]; ];
}, },
iconSize() { iconSize() {

View file

@ -43,12 +43,14 @@ export default {
}, },
computed: { computed: {
style() { style() {
const fontSize = Math.floor(this.size / 2.5);
const lineHeight = this.size + Math.floor(this.size / 20);
let style = { let style = {
width: `${this.size}px`, width: `${this.size}px`,
height: `${this.size}px`, height: `${this.size}px`,
borderRadius: this.rounded ? '50%' : 0, borderRadius: this.rounded ? '50%' : 0,
lineHeight: `${this.size + Math.floor(this.size / 20)}px`, lineHeight: `${Math.max(lineHeight, 12)}px`,
fontSize: `${Math.floor(this.size / 2.5)}px`, fontSize: `${Math.max(fontSize, 8)}px`,
}; };
if (this.backgroundColor) { if (this.backgroundColor) {

View file

@ -1,10 +1,11 @@
<template> <template>
<woot-tabs :index="activeTabIndex" @change="onTabChange"> <woot-tabs class="smooth" :index="activeTabIndex" @change="onTabChange">
<woot-tabs-item <woot-tabs-item
v-for="item in items" v-for="item in items"
:key="item.key" :key="item.key"
:name="item.name" :name="item.name"
:count="item.count" :count="item.count"
variant="smooth"
/> />
</woot-tabs> </woot-tabs>
</template> </template>
@ -48,3 +49,12 @@ export default {
}, },
}; };
</script> </script>
<style lang="scss" scoped>
.smooth {
padding-bottom: var(--space-small);
&::v-deep .button {
padding: var(--space-smaller) var(--space-small);
}
}
</style>

View file

@ -1,8 +1,10 @@
<template> <template>
<div class="inbox--name"> <woot-label
<fluent-icon class="inbox--icon" :icon="computedInboxClass" size="12" /> :title="inbox.name"
{{ inbox.name }} :icon="computedInboxClass"
</div> color-scheme="secondary"
class="inbox--name"
/>
</template> </template>
<script> <script>
import { getInboxClassByType } from 'dashboard/helper/inbox'; import { getInboxClassByType } from 'dashboard/helper/inbox';
@ -23,18 +25,13 @@ export default {
}, },
}; };
</script> </script>
<style scoped> <style lang="scss" scoped>
.inbox--name { .label.secondary.inbox--name {
display: inline-flex;
padding: var(--space-micro) 0;
line-height: var(--space-slab);
font-weight: var(--font-weight-medium);
background: none; background: none;
color: var(--s-500); color: var(--s-600);
font-size: var(--font-size-mini);
}
.inbox--icon { .label__title {
margin-right: var(--space-micro); line-height: 1.2;
}
} }
</style> </style>

View file

@ -32,6 +32,7 @@
import AddLabel from 'shared/components/ui/dropdown/AddLabel'; import AddLabel from 'shared/components/ui/dropdown/AddLabel';
import LabelDropdown from 'shared/components/ui/label/LabelDropdown'; import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
import { mixin as clickaway } from 'vue-clickaway'; import { mixin as clickaway } from 'vue-clickaway';
import { getBleachBgOfHexColor } from 'shared/helpers/ColorHelper';
export default { export default {
components: { components: {
@ -65,6 +66,7 @@ export default {
}, },
methods: { methods: {
getBleachBgOfHexColor,
addItem(label) { addItem(label) {
this.$emit('add', label); this.$emit('add', label);
}, },

View file

@ -11,12 +11,13 @@ describe(`when there are NO errors loading the thumbnail`, () => {
data() { data() {
return { return {
imgError: false, imgError: false,
hasImageLoaded: true,
}; };
}, },
}); });
expect(wrapper.find('#image').exists()).toBe(true); expect(wrapper.find('#image').exists()).toBe(true);
const avatarComponent = wrapper.findComponent(Avatar); const avatarComponent = wrapper.findComponent(Avatar);
expect(avatarComponent.exists()).toBe(false); expect(avatarComponent.isVisible()).toBe(false);
}); });
}); });
@ -29,12 +30,13 @@ describe(`when there ARE errors loading the thumbnail`, () => {
data() { data() {
return { return {
imgError: true, imgError: true,
hasImageLoaded: true,
}; };
}, },
}); });
expect(wrapper.find('#image').exists()).toBe(false); expect(wrapper.find('#image').isVisible()).toBe(false);
const avatarComponent = wrapper.findComponent(Avatar); const avatarComponent = wrapper.findComponent(Avatar);
expect(avatarComponent.exists()).toBe(true); expect(avatarComponent.isVisible()).toBe(true);
}); });
}); });

View file

@ -1,14 +1,15 @@
<template> <template>
<div class="user-thumbnail-box" :style="{ height: size, width: size }"> <div class="user-thumbnail-box" :style="{ height: size, width: size }">
<img <img
v-if="!imgError && Boolean(src)" v-show="shouldShowImage"
id="image" id="image"
:src="src" :src="src"
:class="thumbnailClass" :class="thumbnailClass"
@load="() => onImgLoad()"
@error="onImgError()" @error="onImgError()"
/> />
<Avatar <Avatar
v-else v-show="!shouldShowImage"
:username="username" :username="username"
:class="thumbnailClass" :class="thumbnailClass"
:size="avatarSize" :size="avatarSize"
@ -123,6 +124,7 @@ export default {
data() { data() {
return { return {
imgError: false, imgError: false,
hasImageLoaded: false,
}; };
}, },
computed: { computed: {
@ -147,6 +149,11 @@ export default {
const classname = this.hasBorder ? 'border' : ''; const classname = this.hasBorder ? 'border' : '';
return `user-thumbnail ${classname}`; return `user-thumbnail ${classname}`;
}, },
shouldShowImage() {
if (!this.src) return false;
if (this.hasImageLoaded) return !this.imgError;
return false;
},
}, },
watch: { watch: {
src: { src: {
@ -161,6 +168,9 @@ export default {
onImgError() { onImgError() {
this.imgError = true; this.imgError = true;
}, },
onImgLoad() {
this.hasImageLoaded = true;
},
}, },
}; };
</script> </script>
@ -177,6 +187,7 @@ export default {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
object-fit: cover; object-fit: cover;
vertical-align: initial;
&.border { &.border {
border: 1px solid white; border: 1px solid white;

View file

@ -8,7 +8,7 @@
emoji="😊" emoji="😊"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="tiny"
@click="toggleEmojiPicker" @click="toggleEmojiPicker"
/> />
<!-- ensure the same validations for attachment types are implemented in backend models as well --> <!-- ensure the same validations for attachment types are implemented in backend models as well -->
@ -34,7 +34,7 @@
emoji="📎" emoji="📎"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="tiny"
/> />
</file-upload> </file-upload>
<woot-button <woot-button
@ -44,7 +44,7 @@
emoji="🖊️" emoji="🖊️"
color-scheme="secondary" color-scheme="secondary"
variant="smooth" variant="smooth"
size="small" size="tiny"
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')" :title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
@click="toggleFormatMode" @click="toggleFormatMode"
/> />

View file

@ -24,71 +24,96 @@
v-if="bulkActionCheck" v-if="bulkActionCheck"
:src="currentContact.thumbnail" :src="currentContact.thumbnail"
:badge="inboxBadge" :badge="inboxBadge"
class="columns"
:username="currentContact.name" :username="currentContact.name"
:status="currentContact.availability_status" :status="currentContact.availability_status"
size="40px" size="40px"
/> />
<div class="conversation--details columns"> <div class="message">
<div class="conversation--metadata"> <div class="header">
<inbox-name v-if="showInboxName" :inbox="inbox" /> <div
<span v-if="showAssignee && assignee.name && false"
v-if="showAssignee && assignee.name" class="assignee-name-wrap"
class="label assignee-label text-truncate"
> >
<fluent-icon icon="person" size="12" /> <thumbnail
{{ assignee.name }} v-tooltip.top-end="assignee.name"
</span> class="assignee-avatar"
</div> :src="assignee.thumbnail"
<h4 class="conversation--user"> :username="assignee.name"
{{ currentContact.name }} size="14px"
</h4>
<p v-if="lastMessageInChat" class="conversation--message">
<fluent-icon
v-if="isMessagePrivate"
size="16"
class="message--attachment-icon last-message-icon"
icon="lock-closed"
/>
<fluent-icon
v-else-if="messageByAgent"
size="16"
class="message--attachment-icon last-message-icon"
icon="arrow-reply"
/>
<fluent-icon
v-else-if="isMessageAnActivity"
size="16"
class="message--attachment-icon last-message-icon"
icon="info"
/>
<span v-if="lastMessageInChat.content">
{{ parsedLastMessage }}
</span>
<span v-else-if="lastMessageInChat.attachments">
<fluent-icon
v-if="attachmentIcon"
size="16"
class="message--attachment-icon"
:icon="attachmentIcon"
/> />
{{ this.$t(`${attachmentMessageContent}`) }} <fluent-icon size="10" icon="chevron-right" class="assignee-arrow" />
</div>
<h4 class="user-name text-truncate">
{{ currentContact.name }}
</h4>
<div class="inbox-name-wrap">
<inbox-name v-if="showInboxName" :inbox="inbox" />
</div>
</div>
<div class="content">
<span v-if="unreadCount" class="unread small badge success">
{{ unreadCount > 9 ? '9+' : unreadCount }}
</span> </span>
<span v-else> <p v-if="lastMessageInChat" class="message__content text-truncate">
{{ $t('CHAT_LIST.NO_CONTENT') }} <fluent-icon v-if="isMessagePrivate" size="12" icon="lock-closed" />
</span> <fluent-icon
</p> v-else-if="messageByAgent"
<p v-else class="conversation--message"> size="12"
<fluent-icon size="16" class="message--attachment-icon" icon="info" /> icon="arrow-reply"
<span> />
{{ this.$t(`CHAT_LIST.NO_MESSAGES`) }} <fluent-icon v-else-if="isMessageAnActivity" size="12" icon="info" />
</span> <span
</p> v-if="lastMessageInChat.content"
<div class="conversation--meta"> class="content--text text-truncate"
<span class="timestamp"> >
{{ dynamicTime(chat.timestamp) }} {{ parsedLastMessage }}
</span> </span>
<span class="unread">{{ unreadCount > 9 ? '9+' : unreadCount }}</span> <span
v-else-if="lastMessageInChat.attachments"
class="message--with-icon"
>
<fluent-icon size="12" :icon="attachmentIcon" />
{{ $t(`${attachmentMessageContent}`) }}
</span>
<span v-else class="content--text text-truncate">
{{ $t('CHAT_LIST.NO_CONTENT') }}
</span>
</p>
<p v-else class="message__content">
<fluent-icon size="12" icon="info" />
<span class="content--text text-truncate">
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
</span>
</p>
<div class="meta">
<span class="timestamp">
{{ dynamicTime(chat.timestamp) }}
</span>
</div>
</div>
<div v-if="showAssignee && assignee.name" class="footer">
<div class="assignee-name-wrap">
<fluent-icon size="12" icon="person" class="assignee-arrow" />
<thumbnail
v-if="false"
v-tooltip.top-end="assignee.name"
class="assignee-avatar"
:src="assignee.thumbnail"
:username="assignee.name"
size="14px"
/>
<span class="assignee-name">Nithin David</span>
</div>
<woot-label
v-for="label in activeLabels"
:key="label.id"
:title="label.title"
:description="label.description"
:color="label.color"
variant="smooth"
small
/>
</div> </div>
</div> </div>
</div> </div>
@ -104,6 +129,7 @@ import router from '../../../routes';
import { frontendURL, conversationUrl } from '../../../helper/URLHelper'; import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
import InboxName from '../InboxName'; import InboxName from '../InboxName';
import inboxMixin from 'shared/mixins/inboxMixin'; import inboxMixin from 'shared/mixins/inboxMixin';
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
const ATTACHMENT_ICONS = { const ATTACHMENT_ICONS = {
image: 'image', image: 'image',
@ -120,7 +146,13 @@ export default {
Thumbnail, Thumbnail,
}, },
mixins: [inboxMixin, timeMixin, conversationMixin, messageFormatterMixin], mixins: [
inboxMixin,
timeMixin,
conversationMixin,
messageFormatterMixin,
conversationLabelMixin,
],
props: { props: {
activeLabel: { activeLabel: {
type: String, type: String,
@ -172,6 +204,11 @@ export default {
currentUser: 'getCurrentUser', currentUser: 'getCurrentUser',
accountId: 'getCurrentAccountId', accountId: 'getCurrentAccountId',
}), }),
conversationId() {
return this.chat.id;
},
bulkActionCheck() { bulkActionCheck() {
return !this.hideThumbnail && !this.hovered && !this.selected; return !this.hideThumbnail && !this.hovered && !this.selected;
}, },
@ -260,10 +297,6 @@ export default {
this.inboxesList.length > 1 this.inboxesList.length > 1
); );
}, },
inboxName() {
const stateInbox = this.inbox;
return stateInbox.name || '';
},
}, },
methods: { methods: {
cardClick(chat) { cardClick(chat) {
@ -297,76 +330,187 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.conversation { .conversation {
align-items: center; display: flex;
position: relative;
border-radius: var(--border-radius-medium);
cursor: pointer;
margin: var(--space-smaller) var(--space-small);
padding: var(--space-small);
&:hover { &:hover {
background: var(--color-background-light); background: var(--color-background-light);
} }
&.active {
background: var(--w-25);
}
&.unread-chat {
.message__content {
font-weight: var(--font-weight-medium);
}
.user-name {
font-weight: var(--font-weight-bold);
}
}
&.compact {
padding-left: 0;
margin: var(--space-smaller);
.message {
margin-left: 0;
padding-left: var(--space-small);
}
}
} }
.conversation-selected { .conversation-selected {
background: var(--color-background-light); background: var(--color-background-light);
} }
.has-inbox-name { .message {
&::v-deep .user-thumbnail-box { width: 100%;
margin-top: var(--space-normal); min-width: 0;
align-items: flex-start; margin-left: var(--space-small);
} }
.conversation--meta {
margin-top: var(--space-normal); .message__content {
display: flex;
align-items: center;
color: var(--color-body);
flex-grow: 0;
font-size: var(--font-size-small);
font-weight: var(--font-weight-normal);
height: var(--font-size-medium);
line-height: var(--font-size-medium);
margin: 0;
max-width: 100%;
width: auto;
.fluent-icon {
flex-shrink: 0;
margin-right: var(--space-micro);
} }
} }
.conversation--details { .user-name {
.conversation--user { font-size: var(--font-size-small);
padding-top: var(--space-micro); line-height: var(--font-size-medium);
text-overflow: ellipsis; margin-bottom: 0;
overflow: hidden; text-transform: capitalize;
white-space: nowrap; max-width: 60%;
width: 60%; }
.meta {
display: flex;
flex-grow: 1;
flex-shrink: 0;
align-items: center;
justify-content: flex-end;
padding-left: var(--space-medium);
.timestamp {
color: var(--s-500);
font-size: var(--font-size-mini);
font-weight: var(--font-weight-normal);
line-height: var(--font-size-medium);
} }
} }
.last-message-icon { .header {
color: var(--s-600);
}
.conversation--metadata {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding-right: var(--space-normal); padding-top: var(--space-micro);
}
.content {
display: flex;
align-items: center;
padding: var(--space-micro) 0 var(--space-micro);
.badge {
color: var(--white);
margin-right: var(--space-smaller);
}
}
.assignee-name-wrap {
display: flex;
align-items: center;
margin-right: var(--space-smaller);
margin-bottom: var(--space-smaller);
}
.assignee-arrow {
color: var(--w-700);
}
.assignee-avatar {
margin-right: var(--space-micro);
}
.message--with-icon {
display: inline-flex;
align-items: center;
}
.inbox-name-wrap {
display: flex;
justify-content: flex-end;
align-items: center;
padding-left: var(--space-normal);
margin-left: auto;
.label { .label {
background: none; background: none;
color: var(--s-500); color: var(--s-600);
font-size: var(--font-size-mini); margin-bottom: 0;
font-weight: var(--font-weight-medium); margin-right: 0;
line-height: var(--space-slab); max-width: 12rem;
padding: var(--space-micro) 0 var(--space-micro) 0;
}
.assignee-label {
display: inline-flex;
max-width: 50%;
} }
} }
.message--attachment-icon { .footer {
margin-top: var(--space-minus-micro); display: flex;
vertical-align: middle; align-items: center;
flex-flow: row wrap;
margin-top: var(--space-smaller);
.label {
margin-bottom: var(--space-smaller);
}
}
.assignee-name {
font-size: var(--font-size-mini);
padding-left: var(--space-smaller);
}
.assignee-name-wrap {
border-radius: var(--border-radius-small);
padding: 0 var(--space-smaller) 0 var(--space-smaller);
border: 1px solid var(--w-75);
background: var(--w-25);
color: var(--w-800);
flex-shrink: 0;
font-weight: var(--font-weight-medium);
} }
.checkbox-wrapper { .checkbox-wrapper {
height: 40px;
width: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 100%; flex: 1 0 auto;
margin-top: var(--space-normal);
height: var(--space-large);
width: var(--space-large);
border-radius: var(--space-medium);
margin: var(--space-small) var(--space-smaller) 0;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: var(--w-100); background-color: var(--w-75);
} }
input[type='checkbox'] { input[type='checkbox'] {

View file

@ -20,7 +20,7 @@
/> />
</h3> </h3>
<div class="conversation--header--actions"> <div class="conversation--header--actions">
<inbox-name :inbox="inbox" class="margin-right-small" /> <inbox-name :inbox="inbox" class="header__inbox-name" />
<span <span
v-if="isSnoozed" v-if="isSnoozed"
class="snoozed--display-text margin-right-small" class="snoozed--display-text margin-right-small"
@ -164,7 +164,7 @@ export default {
.user--name { .user--name {
display: inline-block; display: inline-block;
font-size: var(--font-size-medium); font-size: var(--font-size-medium);
line-height: 1.3; line-height: 1.2;
margin: 0; margin: 0;
text-transform: capitalize; text-transform: capitalize;
width: 100%; width: 100%;
@ -174,6 +174,7 @@ export default {
align-items: center; align-items: center;
display: flex; display: flex;
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
padding-top: var(--space-smaller);
.user--profile__button { .user--profile__button {
padding: 0; padding: 0;
@ -185,6 +186,12 @@ export default {
} }
} }
.header__inbox-name {
margin: 0;
margin-right: var(--space-small);
background: transparent;
}
.hmac-warning__icon { .hmac-warning__icon {
color: var(--y-600); color: var(--y-600);
} }

View file

@ -453,16 +453,15 @@ export default {
background: var(--white); background: var(--white);
padding: inherit 0; border-top-left-radius: calc(var(--space-medium) + 1px);
border-top-left-radius: calc( border-bottom-left-radius: calc(var(--space-medium) + 1px);
var(--space-medium) + 1px
); /* 100px of height + 10px of border */
border-bottom-left-radius: calc(
var(--space-medium) + 1px
); /* 100px of height + 10px of border */
border: 1px solid var(--color-border-light); border: 1px solid var(--color-border-light);
border-right: 0; border-right: 0;
box-sizing: border-box; box-sizing: border-box;
&:hover {
padding: var(--space-small);
}
} }
} }
</style> </style>

View file

@ -1,6 +1,6 @@
import fromUnixTime from 'date-fns/fromUnixTime'; import fromUnixTime from 'date-fns/fromUnixTime';
import format from 'date-fns/format'; import format from 'date-fns/format';
import formatDistanceToNow from 'date-fns/formatDistanceToNow'; import formatDistanceToNowStrict from 'date-fns/formatDistanceToNowStrict';
export default { export default {
methods: { methods: {
@ -10,7 +10,7 @@ export default {
}, },
dynamicTime(time) { dynamicTime(time) {
const unixTime = fromUnixTime(time); const unixTime = fromUnixTime(time);
return formatDistanceToNow(unixTime, { addSuffix: true }); return formatDistanceToNowStrict(unixTime, { addSuffix: true });
}, },
}, },
}; };

View file

@ -24,7 +24,7 @@
<div class="contact-actions"> <div class="contact-actions">
<woot-button <woot-button
class="new-message" class="new-message"
size="small expanded" size="tiny expanded"
icon="ion-paper-airplane" icon="ion-paper-airplane"
@click="onNewMessageClick" @click="onNewMessageClick"
> >
@ -32,7 +32,7 @@
</woot-button> </woot-button>
<woot-button <woot-button
variant="hollow" variant="hollow"
size="small expanded" size="tiny expanded"
icon="edit" icon="edit"
@click="onEditClick" @click="onEditClick"
> >

View file

@ -69,7 +69,7 @@
title="$t('CONTACT_PANEL.NEW_MESSAGE')" title="$t('CONTACT_PANEL.NEW_MESSAGE')"
class="new-message" class="new-message"
icon="chat" icon="chat"
size="small" size="tiny"
@click="toggleConversationModal" @click="toggleConversationModal"
/> />
<woot-button <woot-button
@ -78,7 +78,7 @@
class="edit-contact" class="edit-contact"
icon="edit" icon="edit"
variant="smooth" variant="smooth"
size="small" size="tiny"
@click="toggleEditModal" @click="toggleEditModal"
/> />
<woot-button <woot-button
@ -88,7 +88,7 @@
class="merge-contact" class="merge-contact"
icon="merge" icon="merge"
variant="smooth" variant="smooth"
size="small" size="tiny"
color-scheme="secondary" color-scheme="secondary"
:disabled="uiFlags.isMerging" :disabled="uiFlags.isMerging"
@click="openMergeModal" @click="openMergeModal"
@ -100,7 +100,7 @@
class="delete-contact" class="delete-contact"
icon="delete" icon="delete"
variant="smooth" variant="smooth"
size="small" size="tiny"
color-scheme="alert" color-scheme="alert"
:disabled="uiFlags.isDeleting" :disabled="uiFlags.isDeleting"
@click="toggleDeleteModal" @click="toggleDeleteModal"

View file

@ -48,6 +48,10 @@ import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
import AddLabel from 'shared/components/ui/dropdown/AddLabel'; import AddLabel from 'shared/components/ui/dropdown/AddLabel';
import { mixin as clickaway } from 'vue-clickaway'; import { mixin as clickaway } from 'vue-clickaway';
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin'; import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
import {
getBleachBgOfHexColor,
getTextShadeOfHexColor,
} from 'shared/helpers/ColorHelper';
export default { export default {
components: { components: {
@ -78,6 +82,8 @@ export default {
}), }),
}, },
methods: { methods: {
getBleachBgOfHexColor,
getTextShadeOfHexColor,
toggleLabels() { toggleLabels() {
this.showSearchDropdownLabel = !this.showSearchDropdownLabel; this.showSearchDropdownLabel = !this.showSearchDropdownLabel;
}, },

View file

@ -10,7 +10,6 @@
--space-normal: 1.6rem; --space-normal: 1.6rem;
--space-two: 2rem; --space-two: 2rem;
--space-medium: 2.4rem; --space-medium: 2.4rem;
--space-three: 3rem;
--space-large: 3.2rem; --space-large: 3.2rem;
--space-larger: 4.8rem; --space-larger: 4.8rem;
--space-jumbo: 6.4rem; --space-jumbo: 6.4rem;

View file

@ -5,6 +5,7 @@
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="fluent-icon"
> >
<path <path
v-for="source in pathSource" v-for="source in pathSource"

View file

@ -24,5 +24,6 @@ export default {
.label--add { .label--add {
margin-bottom: var(--space-micro); margin-bottom: var(--space-micro);
margin-right: var(--space-micro); margin-right: var(--space-micro);
height: var(--space-two);
} }
</style> </style>

View file

@ -0,0 +1,70 @@
export const hexToRGB = H => {
let r = 0;
let g = 0;
let b = 0;
if (H.length === 4) {
r = '0x' + H[1] + H[1];
g = '0x' + H[2] + H[2];
b = '0x' + H[3] + H[3];
} else if (H.length === 7) {
r = '0x' + H[1] + H[2];
g = '0x' + H[3] + H[4];
b = '0x' + H[5] + H[6];
}
return [r, g, b];
};
export const rgbToHSL = (r, g, b) => {
let cmin = Math.min(r, g, b);
let cmax = Math.max(r, g, b);
let delta = cmax - cmin;
let h = 0;
let s = 0;
let l = 0;
if (delta === 0) h = 0;
else if (cmax === r) h = ((g - b) / delta) % 6;
else if (cmax === g) h = (b - r) / delta + 2;
else h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0) h += 360;
l = (cmax + cmin) / 2;
s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return [h, s, l];
};
export const hexToHSLAsArray = H => {
// Convert hex to RGB first
let [r, g, b] = hexToRGB(H);
// Then to HSL
r /= 255;
g /= 255;
b /= 255;
return rgbToHSL(r, g, b);
};
export const hexToHSL = hex => {
const [h, s, l] = hexToHSLAsArray(hex);
return 'hsl(' + h + ',' + s + '%,' + l + '%)';
};
export const getTextShadeOfHexColor = (hex, shade = 24) => {
const [h, s, l] = hexToHSLAsArray(hex);
const newL = Math.min(36, Math.max(l - shade, 40));
return 'hsl(' + h + ',' + s + '%,' + newL + '%)';
};
export const getBleachBgOfHexColor = (hex, bleach = 35) => {
const [h, s, l] = hexToHSLAsArray(hex);
const newL = Math.max(94, Math.min(l + bleach, 96));
return 'hsl(' + h + ',' + s + '%,' + newL + '%)';
};

View file

@ -0,0 +1,34 @@
import {
hexToHSLAsArray,
hexToHSL,
getTextShadeOfHexColor,
getBleachBgOfHexColor,
} from '../ColorHelper';
describe('#hexToHSLAsArray', () => {
it('should return correct color conversion for 6 digit hex', () => {
expect(hexToHSLAsArray('#ffffff')).toEqual([0, 0, 100]);
});
it('should return correct color conversion for 3 digit hex', () => {
expect(hexToHSLAsArray('#fff')).toEqual([0, 0, 100]);
});
});
describe('#hexToHSL', () => {
it('should return correct color conversion for 6 digit hex to hsl string', () => {
expect(hexToHSL('#ffffff')).toEqual('hsl(0,0%,100%)');
});
});
describe('#getTextShadeOfHexColor', () => {
it('should return correct color shade for 6 digit hex to hsl string', () => {
expect(getTextShadeOfHexColor('#ffffff')).toEqual('hsl(0,0%,22%)');
});
});
describe('#getBleachBgOfHexColor', () => {
it('should return correct color shade for 6 digit hex to hsl string', () => {
expect(getBleachBgOfHexColor('#ffffff')).toEqual('hsl(0,0%,96%)');
});
});