Compare commits
41 commits
develop
...
chore/chat
Author | SHA1 | Date | |
---|---|---|---|
|
3452a07935 | ||
|
bc14f6d542 | ||
|
52eb4c3183 | ||
|
8e81ef5951 | ||
|
8a57a53e3d | ||
|
b570f3db22 | ||
|
4906d5b080 | ||
|
7e7f6631b3 | ||
|
e1d8f32e94 | ||
|
5755a3804e | ||
|
fc12580b3e | ||
|
08f76206cf | ||
|
7e8de04448 | ||
|
15950ac8c4 | ||
|
7c3fcbba2e | ||
|
12b6a42aa1 | ||
|
6012b90c78 | ||
|
f1c61cf306 | ||
|
00b9803959 | ||
|
463ac25051 | ||
|
f84bcbfc7c | ||
|
b900e38022 | ||
|
1e9fa55bba | ||
|
8abf171409 | ||
|
50b70093e8 | ||
|
39254be442 | ||
|
9793a4d71c | ||
|
eb9d9ace96 | ||
|
c17b532f95 | ||
|
c327a7c20e | ||
|
fee43711cc | ||
|
b0a0d08d8d | ||
|
c316c53595 | ||
|
b81affbbbf | ||
|
aadbc3e0f4 | ||
|
e4f52fb183 | ||
|
0f2f8a188b | ||
|
9977884a2c | ||
|
4aab553567 | ||
|
034701db8f | ||
|
2be97cf50a |
31 changed files with 572 additions and 340 deletions
|
@ -56,10 +56,60 @@ code {
|
|||
padding-right: var(--space-normal);
|
||||
}
|
||||
|
||||
$badge-size: var(--space-normal);
|
||||
$label-badge-size: var(--space-slab);
|
||||
|
||||
.badge {
|
||||
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: var(--space-one);
|
||||
}
|
||||
|
|
|
@ -220,10 +220,10 @@ $accordionmenu-arrow-size: 6px;
|
|||
|
||||
$badge-background: $primary-color;
|
||||
$badge-color: $white;
|
||||
$badge-color-alt: $black;
|
||||
$badge-color-alt: $color-body;
|
||||
$badge-palette: $foundation-palette;
|
||||
$badge-padding: var(--space-smaller);
|
||||
$badge-minwidth: 2.1em;
|
||||
$badge-minwidth: var(--space-normal);
|
||||
$badge-font-size: var(--font-size-nano);
|
||||
|
||||
// 10. Breadcrumbs
|
||||
|
|
|
@ -37,6 +37,10 @@ $default-button-height: 4.0rem;
|
|||
border-radius: $space-larger;
|
||||
}
|
||||
|
||||
&.not-rounded {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
// @TODO Use with link
|
||||
|
||||
&.compact {
|
||||
|
|
|
@ -11,138 +11,10 @@
|
|||
}
|
||||
|
||||
.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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,11 +86,6 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.page-title {
|
||||
margin-bottom: $zero;
|
||||
margin-left: $space-normal;
|
||||
}
|
||||
|
||||
.status--filter {
|
||||
@include padding($zero null $zero $space-normal);
|
||||
@include margin($zero);
|
||||
|
|
|
@ -6,22 +6,7 @@
|
|||
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 {
|
||||
@include margin($zero $space-slab);
|
||||
|
||||
.badge {
|
||||
background: $color-background;
|
||||
|
@ -33,36 +18,19 @@
|
|||
padding: $space-smaller;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
a {
|
||||
color: darken($medium-gray, 20%);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
a,
|
||||
.button {
|
||||
@include position(relative, 1px null null null);
|
||||
align-items: center;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: $medium-gray;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: $font-size-small;
|
||||
height: var(--space-large);
|
||||
transition: border-color .15s $swift-ease-out-function;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
a {
|
||||
|
||||
a,
|
||||
.button {
|
||||
border-bottom-color: $color-woot;
|
||||
color: $color-woot;
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
@ -70,4 +38,26 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
class="chat-list__top"
|
||||
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
|
||||
>
|
||||
<h1 class="page-title text-truncate" :title="pageTitle">
|
||||
<h1 class="page-sub-title text-truncate" :title="pageTitle">
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
|
||||
|
@ -77,7 +77,6 @@
|
|||
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||
:items="assigneeTabItems"
|
||||
:active-tab="activeAssigneeTab"
|
||||
class="tab--chat-type"
|
||||
@chatTabChange="updateAssigneeTab"
|
||||
/>
|
||||
|
||||
|
@ -658,7 +657,7 @@ export default {
|
|||
width: 35rem;
|
||||
}
|
||||
@include breakpoint(xxlarge up) {
|
||||
width: 38rem;
|
||||
width: 36rem;
|
||||
}
|
||||
@include breakpoint(xxxlarge up) {
|
||||
flex-basis: 46rem;
|
||||
|
@ -678,6 +677,9 @@ export default {
|
|||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.page-sub-title {
|
||||
margin-left: var(--space-normal);
|
||||
}
|
||||
.delete-custom-view__button {
|
||||
margin-right: var(--space-normal);
|
||||
}
|
||||
|
|
|
@ -85,9 +85,6 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
$badge-size: var(--space-normal);
|
||||
$label-badge-size: var(--space-slab);
|
||||
|
||||
.button {
|
||||
margin: var(--space-small) 0;
|
||||
}
|
||||
|
@ -127,6 +124,7 @@ $label-badge-size: var(--space-slab);
|
|||
font-size: var(--font-size-nano);
|
||||
}
|
||||
|
||||
|
||||
.badge--label,
|
||||
.badge--icon {
|
||||
display: inline-flex;
|
||||
|
|
|
@ -171,7 +171,7 @@ export default {
|
|||
}
|
||||
|
||||
.secondary-menu--title {
|
||||
color: var(--s-600);
|
||||
color: var(--s-700);
|
||||
display: flex;
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--space-two);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
:style="{ background: color }"
|
||||
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>
|
||||
<button
|
||||
v-if="showClose"
|
||||
|
@ -112,16 +112,16 @@ export default {
|
|||
background: var(--s-50);
|
||||
color: var(--s-800);
|
||||
border: 1px solid var(--s-75);
|
||||
height: var(--space-medium);
|
||||
height: var(--space-two);
|
||||
line-height: 1.2;
|
||||
|
||||
&.small {
|
||||
font-size: var(--font-size-micro);
|
||||
padding: var(--space-micro) var(--space-smaller);
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0.15px;
|
||||
}
|
||||
|
||||
.label--icon {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
|
@ -141,16 +141,14 @@ export default {
|
|||
/* Color Schemes */
|
||||
&.primary {
|
||||
background: var(--w-100);
|
||||
color: var(--w-900);
|
||||
border: 1px solid var(--w-200);
|
||||
color: var(--w-700);
|
||||
a {
|
||||
color: var(--w-900);
|
||||
}
|
||||
}
|
||||
&.secondary {
|
||||
background: var(--s-100);
|
||||
color: var(--s-900);
|
||||
border: 1px solid var(--s-200);
|
||||
color: var(--s-700);
|
||||
a {
|
||||
color: var(--s-900);
|
||||
}
|
||||
|
@ -158,7 +156,6 @@ export default {
|
|||
&.success {
|
||||
background: var(--g-100);
|
||||
color: var(--g-900);
|
||||
border: 1px solid var(--g-200);
|
||||
a {
|
||||
color: var(--g-900);
|
||||
}
|
||||
|
@ -166,7 +163,7 @@ export default {
|
|||
&.alert {
|
||||
background: var(--r-100);
|
||||
color: var(--r-900);
|
||||
border: 1px solid var(--r-200);
|
||||
|
||||
a {
|
||||
color: var(--r-900);
|
||||
}
|
||||
|
@ -174,7 +171,6 @@ export default {
|
|||
&.warning {
|
||||
background: var(--y-100);
|
||||
color: var(--y-900);
|
||||
border: 1px solid var(--y-200);
|
||||
a {
|
||||
color: var(--y-900);
|
||||
}
|
||||
|
@ -198,7 +194,15 @@ export default {
|
|||
}
|
||||
|
||||
.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 {
|
||||
|
|
|
@ -3,14 +3,21 @@
|
|||
:class="{
|
||||
'tabs-title': true,
|
||||
'is-active': active,
|
||||
[variant]: variant,
|
||||
}"
|
||||
>
|
||||
<a @click="onTabClick">
|
||||
<woot-button
|
||||
:size="size"
|
||||
:variant="buttonVariant"
|
||||
:color-scheme="colorScheme"
|
||||
:is-rounded="isRounded"
|
||||
@click="onTabClick"
|
||||
>
|
||||
{{ name }}
|
||||
<span v-if="showBadge" class="badge">
|
||||
{{ getItemCount }}
|
||||
</span>
|
||||
</a>
|
||||
</woot-button>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -37,6 +44,15 @@ export default {
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: '',
|
||||
// Available variants: [ '', 'smooth']
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -47,6 +63,17 @@ export default {
|
|||
getItemCount() {
|
||||
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: {
|
||||
|
|
|
@ -60,6 +60,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isRounded: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
variantClasses() {
|
||||
|
@ -84,6 +88,7 @@ export default {
|
|||
this.classNames,
|
||||
this.isDisabled ? 'disabled' : '',
|
||||
this.isExpanded ? 'expanded' : '',
|
||||
this.isRounded ? '' : 'not-rounded',
|
||||
];
|
||||
},
|
||||
iconSize() {
|
||||
|
|
|
@ -43,12 +43,14 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
style() {
|
||||
const fontSize = Math.floor(this.size / 2.5);
|
||||
const lineHeight = this.size + Math.floor(this.size / 20);
|
||||
let style = {
|
||||
width: `${this.size}px`,
|
||||
height: `${this.size}px`,
|
||||
borderRadius: this.rounded ? '50%' : 0,
|
||||
lineHeight: `${this.size + Math.floor(this.size / 20)}px`,
|
||||
fontSize: `${Math.floor(this.size / 2.5)}px`,
|
||||
lineHeight: `${Math.max(lineHeight, 12)}px`,
|
||||
fontSize: `${Math.max(fontSize, 8)}px`,
|
||||
};
|
||||
|
||||
if (this.backgroundColor) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<template>
|
||||
<woot-tabs :index="activeTabIndex" @change="onTabChange">
|
||||
<woot-tabs class="smooth" :index="activeTabIndex" @change="onTabChange">
|
||||
<woot-tabs-item
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:name="item.name"
|
||||
:count="item.count"
|
||||
variant="smooth"
|
||||
/>
|
||||
</woot-tabs>
|
||||
</template>
|
||||
|
@ -48,3 +49,12 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.smooth {
|
||||
padding-bottom: var(--space-small);
|
||||
|
||||
&::v-deep .button {
|
||||
padding: var(--space-smaller) var(--space-small);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<template>
|
||||
<div class="inbox--name">
|
||||
<fluent-icon class="inbox--icon" :icon="computedInboxClass" size="12" />
|
||||
{{ inbox.name }}
|
||||
</div>
|
||||
<woot-label
|
||||
:title="inbox.name"
|
||||
:icon="computedInboxClass"
|
||||
color-scheme="secondary"
|
||||
class="inbox--name"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||
|
@ -23,18 +25,13 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.inbox--name {
|
||||
display: inline-flex;
|
||||
padding: var(--space-micro) 0;
|
||||
line-height: var(--space-slab);
|
||||
font-weight: var(--font-weight-medium);
|
||||
<style lang="scss" scoped>
|
||||
.label.secondary.inbox--name {
|
||||
background: none;
|
||||
color: var(--s-500);
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
color: var(--s-600);
|
||||
|
||||
.inbox--icon {
|
||||
margin-right: var(--space-micro);
|
||||
.label__title {
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { getBleachBgOfHexColor } from 'shared/helpers/ColorHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -65,6 +66,7 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
getBleachBgOfHexColor,
|
||||
addItem(label) {
|
||||
this.$emit('add', label);
|
||||
},
|
||||
|
|
|
@ -11,12 +11,13 @@ describe(`when there are NO errors loading the thumbnail`, () => {
|
|||
data() {
|
||||
return {
|
||||
imgError: false,
|
||||
hasImageLoaded: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('#image').exists()).toBe(true);
|
||||
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() {
|
||||
return {
|
||||
imgError: true,
|
||||
hasImageLoaded: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('#image').exists()).toBe(false);
|
||||
expect(wrapper.find('#image').isVisible()).toBe(false);
|
||||
const avatarComponent = wrapper.findComponent(Avatar);
|
||||
expect(avatarComponent.exists()).toBe(true);
|
||||
expect(avatarComponent.isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<template>
|
||||
<div class="user-thumbnail-box" :style="{ height: size, width: size }">
|
||||
<img
|
||||
v-if="!imgError && Boolean(src)"
|
||||
v-show="shouldShowImage"
|
||||
id="image"
|
||||
:src="src"
|
||||
:class="thumbnailClass"
|
||||
@load="() => onImgLoad()"
|
||||
@error="onImgError()"
|
||||
/>
|
||||
<Avatar
|
||||
v-else
|
||||
v-show="!shouldShowImage"
|
||||
:username="username"
|
||||
:class="thumbnailClass"
|
||||
:size="avatarSize"
|
||||
|
@ -123,6 +124,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
imgError: false,
|
||||
hasImageLoaded: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -147,6 +149,11 @@ export default {
|
|||
const classname = this.hasBorder ? 'border' : '';
|
||||
return `user-thumbnail ${classname}`;
|
||||
},
|
||||
shouldShowImage() {
|
||||
if (!this.src) return false;
|
||||
if (this.hasImageLoaded) return !this.imgError;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
src: {
|
||||
|
@ -161,6 +168,9 @@ export default {
|
|||
onImgError() {
|
||||
this.imgError = true;
|
||||
},
|
||||
onImgLoad() {
|
||||
this.hasImageLoaded = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -177,6 +187,7 @@ export default {
|
|||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
object-fit: cover;
|
||||
vertical-align: initial;
|
||||
|
||||
&.border {
|
||||
border: 1px solid white;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
emoji="😊"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
size="tiny"
|
||||
@click="toggleEmojiPicker"
|
||||
/>
|
||||
<!-- ensure the same validations for attachment types are implemented in backend models as well -->
|
||||
|
@ -34,7 +34,7 @@
|
|||
emoji="📎"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
size="tiny"
|
||||
/>
|
||||
</file-upload>
|
||||
<woot-button
|
||||
|
@ -44,7 +44,7 @@
|
|||
emoji="🖊️"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
size="tiny"
|
||||
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||
@click="toggleFormatMode"
|
||||
/>
|
||||
|
|
|
@ -24,71 +24,96 @@
|
|||
v-if="bulkActionCheck"
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
class="columns"
|
||||
:username="currentContact.name"
|
||||
:status="currentContact.availability_status"
|
||||
size="40px"
|
||||
/>
|
||||
<div class="conversation--details columns">
|
||||
<div class="conversation--metadata">
|
||||
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
||||
<span
|
||||
v-if="showAssignee && assignee.name"
|
||||
class="label assignee-label text-truncate"
|
||||
<div class="message">
|
||||
<div class="header">
|
||||
<div
|
||||
v-if="showAssignee && assignee.name && false"
|
||||
class="assignee-name-wrap"
|
||||
>
|
||||
<fluent-icon icon="person" size="12" />
|
||||
{{ assignee.name }}
|
||||
</span>
|
||||
</div>
|
||||
<h4 class="conversation--user">
|
||||
{{ currentContact.name }}
|
||||
</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"
|
||||
<thumbnail
|
||||
v-tooltip.top-end="assignee.name"
|
||||
class="assignee-avatar"
|
||||
:src="assignee.thumbnail"
|
||||
:username="assignee.name"
|
||||
size="14px"
|
||||
/>
|
||||
{{ 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 v-else>
|
||||
{{ $t('CHAT_LIST.NO_CONTENT') }}
|
||||
</span>
|
||||
</p>
|
||||
<p v-else class="conversation--message">
|
||||
<fluent-icon size="16" class="message--attachment-icon" icon="info" />
|
||||
<span>
|
||||
{{ this.$t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||
</span>
|
||||
</p>
|
||||
<div class="conversation--meta">
|
||||
<span class="timestamp">
|
||||
{{ dynamicTime(chat.timestamp) }}
|
||||
</span>
|
||||
<span class="unread">{{ unreadCount > 9 ? '9+' : unreadCount }}</span>
|
||||
<p v-if="lastMessageInChat" class="message__content text-truncate">
|
||||
<fluent-icon v-if="isMessagePrivate" size="12" icon="lock-closed" />
|
||||
<fluent-icon
|
||||
v-else-if="messageByAgent"
|
||||
size="12"
|
||||
icon="arrow-reply"
|
||||
/>
|
||||
<fluent-icon v-else-if="isMessageAnActivity" size="12" icon="info" />
|
||||
<span
|
||||
v-if="lastMessageInChat.content"
|
||||
class="content--text text-truncate"
|
||||
>
|
||||
{{ parsedLastMessage }}
|
||||
</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>
|
||||
|
@ -104,6 +129,7 @@ import router from '../../../routes';
|
|||
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
||||
import InboxName from '../InboxName';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
|
||||
|
||||
const ATTACHMENT_ICONS = {
|
||||
image: 'image',
|
||||
|
@ -120,7 +146,13 @@ export default {
|
|||
Thumbnail,
|
||||
},
|
||||
|
||||
mixins: [inboxMixin, timeMixin, conversationMixin, messageFormatterMixin],
|
||||
mixins: [
|
||||
inboxMixin,
|
||||
timeMixin,
|
||||
conversationMixin,
|
||||
messageFormatterMixin,
|
||||
conversationLabelMixin,
|
||||
],
|
||||
props: {
|
||||
activeLabel: {
|
||||
type: String,
|
||||
|
@ -172,6 +204,11 @@ export default {
|
|||
currentUser: 'getCurrentUser',
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
|
||||
conversationId() {
|
||||
return this.chat.id;
|
||||
},
|
||||
|
||||
bulkActionCheck() {
|
||||
return !this.hideThumbnail && !this.hovered && !this.selected;
|
||||
},
|
||||
|
@ -260,10 +297,6 @@ export default {
|
|||
this.inboxesList.length > 1
|
||||
);
|
||||
},
|
||||
inboxName() {
|
||||
const stateInbox = this.inbox;
|
||||
return stateInbox.name || '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cardClick(chat) {
|
||||
|
@ -297,76 +330,187 @@ export default {
|
|||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.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 {
|
||||
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 {
|
||||
background: var(--color-background-light);
|
||||
}
|
||||
|
||||
.has-inbox-name {
|
||||
&::v-deep .user-thumbnail-box {
|
||||
margin-top: var(--space-normal);
|
||||
align-items: flex-start;
|
||||
}
|
||||
.conversation--meta {
|
||||
margin-top: var(--space-normal);
|
||||
.message {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
margin-left: var(--space-small);
|
||||
}
|
||||
|
||||
.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 {
|
||||
.conversation--user {
|
||||
padding-top: var(--space-micro);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 60%;
|
||||
.user-name {
|
||||
font-size: var(--font-size-small);
|
||||
line-height: var(--font-size-medium);
|
||||
margin-bottom: 0;
|
||||
text-transform: capitalize;
|
||||
max-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 {
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
.conversation--metadata {
|
||||
.header {
|
||||
display: flex;
|
||||
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 {
|
||||
background: none;
|
||||
color: var(--s-500);
|
||||
font-size: var(--font-size-mini);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: var(--space-slab);
|
||||
padding: var(--space-micro) 0 var(--space-micro) 0;
|
||||
}
|
||||
|
||||
.assignee-label {
|
||||
display: inline-flex;
|
||||
max-width: 50%;
|
||||
color: var(--s-600);
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
max-width: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
.message--attachment-icon {
|
||||
margin-top: var(--space-minus-micro);
|
||||
vertical-align: middle;
|
||||
.footer {
|
||||
display: flex;
|
||||
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 {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 100%;
|
||||
margin-top: var(--space-normal);
|
||||
flex: 1 0 auto;
|
||||
|
||||
height: var(--space-large);
|
||||
width: var(--space-large);
|
||||
border-radius: var(--space-medium);
|
||||
|
||||
margin: var(--space-small) var(--space-smaller) 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--w-100);
|
||||
background-color: var(--w-75);
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
/>
|
||||
</h3>
|
||||
<div class="conversation--header--actions">
|
||||
<inbox-name :inbox="inbox" class="margin-right-small" />
|
||||
<inbox-name :inbox="inbox" class="header__inbox-name" />
|
||||
<span
|
||||
v-if="isSnoozed"
|
||||
class="snoozed--display-text margin-right-small"
|
||||
|
@ -164,7 +164,7 @@ export default {
|
|||
.user--name {
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-medium);
|
||||
line-height: 1.3;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
width: 100%;
|
||||
|
@ -174,6 +174,7 @@ export default {
|
|||
align-items: center;
|
||||
display: flex;
|
||||
font-size: var(--font-size-mini);
|
||||
padding-top: var(--space-smaller);
|
||||
|
||||
.user--profile__button {
|
||||
padding: 0;
|
||||
|
@ -185,6 +186,12 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.header__inbox-name {
|
||||
margin: 0;
|
||||
margin-right: var(--space-small);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hmac-warning__icon {
|
||||
color: var(--y-600);
|
||||
}
|
||||
|
|
|
@ -453,16 +453,15 @@ export default {
|
|||
|
||||
background: var(--white);
|
||||
|
||||
padding: inherit 0;
|
||||
border-top-left-radius: calc(
|
||||
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-top-left-radius: calc(var(--space-medium) + 1px);
|
||||
border-bottom-left-radius: calc(var(--space-medium) + 1px);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-right: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
padding: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import fromUnixTime from 'date-fns/fromUnixTime';
|
||||
import format from 'date-fns/format';
|
||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||
import formatDistanceToNowStrict from 'date-fns/formatDistanceToNowStrict';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
|
@ -10,7 +10,7 @@ export default {
|
|||
},
|
||||
dynamicTime(time) {
|
||||
const unixTime = fromUnixTime(time);
|
||||
return formatDistanceToNow(unixTime, { addSuffix: true });
|
||||
return formatDistanceToNowStrict(unixTime, { addSuffix: true });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div class="contact-actions">
|
||||
<woot-button
|
||||
class="new-message"
|
||||
size="small expanded"
|
||||
size="tiny expanded"
|
||||
icon="ion-paper-airplane"
|
||||
@click="onNewMessageClick"
|
||||
>
|
||||
|
@ -32,7 +32,7 @@
|
|||
</woot-button>
|
||||
<woot-button
|
||||
variant="hollow"
|
||||
size="small expanded"
|
||||
size="tiny expanded"
|
||||
icon="edit"
|
||||
@click="onEditClick"
|
||||
>
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
title="$t('CONTACT_PANEL.NEW_MESSAGE')"
|
||||
class="new-message"
|
||||
icon="chat"
|
||||
size="small"
|
||||
size="tiny"
|
||||
@click="toggleConversationModal"
|
||||
/>
|
||||
<woot-button
|
||||
|
@ -78,7 +78,7 @@
|
|||
class="edit-contact"
|
||||
icon="edit"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
size="tiny"
|
||||
@click="toggleEditModal"
|
||||
/>
|
||||
<woot-button
|
||||
|
@ -88,7 +88,7 @@
|
|||
class="merge-contact"
|
||||
icon="merge"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
size="tiny"
|
||||
color-scheme="secondary"
|
||||
:disabled="uiFlags.isMerging"
|
||||
@click="openMergeModal"
|
||||
|
@ -100,7 +100,7 @@
|
|||
class="delete-contact"
|
||||
icon="delete"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
size="tiny"
|
||||
color-scheme="alert"
|
||||
:disabled="uiFlags.isDeleting"
|
||||
@click="toggleDeleteModal"
|
||||
|
|
|
@ -48,6 +48,10 @@ import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
|||
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
|
||||
import {
|
||||
getBleachBgOfHexColor,
|
||||
getTextShadeOfHexColor,
|
||||
} from 'shared/helpers/ColorHelper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -78,6 +82,8 @@ export default {
|
|||
}),
|
||||
},
|
||||
methods: {
|
||||
getBleachBgOfHexColor,
|
||||
getTextShadeOfHexColor,
|
||||
toggleLabels() {
|
||||
this.showSearchDropdownLabel = !this.showSearchDropdownLabel;
|
||||
},
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
--space-normal: 1.6rem;
|
||||
--space-two: 2rem;
|
||||
--space-medium: 2.4rem;
|
||||
--space-three: 3rem;
|
||||
--space-large: 3.2rem;
|
||||
--space-larger: 4.8rem;
|
||||
--space-jumbo: 6.4rem;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="fluent-icon"
|
||||
>
|
||||
<path
|
||||
v-for="source in pathSource"
|
||||
|
|
|
@ -24,5 +24,6 @@ export default {
|
|||
.label--add {
|
||||
margin-bottom: var(--space-micro);
|
||||
margin-right: var(--space-micro);
|
||||
height: var(--space-two);
|
||||
}
|
||||
</style>
|
||||
|
|
70
app/javascript/shared/helpers/ColorHelper.js
Normal file
70
app/javascript/shared/helpers/ColorHelper.js
Normal 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 + '%)';
|
||||
};
|
34
app/javascript/shared/helpers/specs/ColorHelper.spec.js
Normal file
34
app/javascript/shared/helpers/specs/ColorHelper.spec.js
Normal 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%)');
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue