feat: Support Dark mode for the widget (#4137)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
3813b3b372
commit
caee9535f1
36 changed files with 411 additions and 113 deletions
|
@ -3,6 +3,7 @@ class WidgetTestsController < ActionController::Base
|
|||
before_action :ensure_widget_position
|
||||
before_action :ensure_widget_type
|
||||
before_action :ensure_widget_style
|
||||
before_action :ensure_dark_mode
|
||||
|
||||
def index
|
||||
render
|
||||
|
@ -14,6 +15,10 @@ class WidgetTestsController < ActionController::Base
|
|||
@widget_style = params[:widget_style] || 'standard'
|
||||
end
|
||||
|
||||
def ensure_dark_mode
|
||||
@dark_mode = params[:dark_mode] || 'light'
|
||||
end
|
||||
|
||||
def ensure_widget_position
|
||||
@widget_position = params[:position] || 'left'
|
||||
end
|
||||
|
|
|
@ -35,7 +35,9 @@
|
|||
</td>
|
||||
<!-- Agent Name + Email -->
|
||||
<td>
|
||||
<span class="agent-name">{{ agent.name }}</span>
|
||||
<span class="agent-name">
|
||||
{{ agent.name }}
|
||||
</span>
|
||||
<span>{{ agent.email }}</span>
|
||||
</td>
|
||||
<!-- Agent Role + Verification Status -->
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import Cookies from 'js-cookie';
|
||||
import { IFrameHelper } from '../sdk/IFrameHelper';
|
||||
import { getBubbleView } from '../sdk/settingsHelper';
|
||||
import {
|
||||
getBubbleView,
|
||||
getDarkMode,
|
||||
getWidgetStyle,
|
||||
} from '../sdk/settingsHelper';
|
||||
import {
|
||||
computeHashForUserData,
|
||||
getUserCookieName,
|
||||
|
@ -24,8 +28,9 @@ const runSDK = ({ baseUrl, websiteToken }) => {
|
|||
type: getBubbleView(chatwootSettings.type),
|
||||
launcherTitle: chatwootSettings.launcherTitle || '',
|
||||
showPopoutButton: chatwootSettings.showPopoutButton || false,
|
||||
widgetStyle: chatwootSettings.widgetStyle || 'standard',
|
||||
widgetStyle: getWidgetStyle(chatwootSettings.widgetStyle) || 'standard',
|
||||
resetTriggered: false,
|
||||
darkMode: getDarkMode(chatwootSettings.darkMode),
|
||||
|
||||
toggle(state) {
|
||||
IFrameHelper.events.toggleBubble(state);
|
||||
|
|
|
@ -145,6 +145,7 @@ export const IFrameHelper = {
|
|||
hideMessageBubble: window.$chatwoot.hideMessageBubble,
|
||||
showPopoutButton: window.$chatwoot.showPopoutButton,
|
||||
widgetStyle: window.$chatwoot.widgetStyle,
|
||||
darkMode: window.$chatwoot.darkMode,
|
||||
});
|
||||
IFrameHelper.onLoad({
|
||||
widgetColor: message.config.channelConfig.widgetColor,
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export const BUBBLE_DESIGN = ['standard', 'expanded_bubble'];
|
||||
export const WIDGET_DESIGN = ['standard', 'flat'];
|
||||
export const DARK_MODE = ['light', 'auto'];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { BUBBLE_DESIGN, WIDGET_DESIGN } from './constants';
|
||||
import { BUBBLE_DESIGN, DARK_MODE, WIDGET_DESIGN } from './constants';
|
||||
|
||||
export const getBubbleView = type =>
|
||||
BUBBLE_DESIGN.includes(type) ? type : BUBBLE_DESIGN[0];
|
||||
|
@ -9,3 +9,6 @@ export const getWidgetStyle = style =>
|
|||
WIDGET_DESIGN.includes(style) ? style : WIDGET_DESIGN[0];
|
||||
|
||||
export const isFlatWidgetStyle = style => style === 'flat';
|
||||
|
||||
export const getDarkMode = darkMode =>
|
||||
DARK_MODE.includes(darkMode) ? darkMode : DARK_MODE[0];
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<div class="card-message chat-bubble agent">
|
||||
<div
|
||||
class="card-message chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<img class="media" :src="mediaUrl" />
|
||||
<div class="card-body">
|
||||
<h4 class="title">
|
||||
<h4 class="title" :class="$dm('text-black-900', 'dark:text-slate-50')">
|
||||
{{ title }}
|
||||
</h4>
|
||||
<p class="body">
|
||||
<p class="body" :class="$dm('text-black-700', 'dark:text-slate-100')">
|
||||
{{ description }}
|
||||
</p>
|
||||
<card-button
|
||||
|
@ -19,11 +22,13 @@
|
|||
|
||||
<script>
|
||||
import CardButton from 'shared/components/CardButton';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardButton,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
@ -52,7 +57,6 @@ export default {
|
|||
@import '~dashboard/assets/scss/mixins.scss';
|
||||
|
||||
.card-message {
|
||||
background: white;
|
||||
max-width: 220px;
|
||||
padding: $space-small;
|
||||
border-radius: $space-small;
|
||||
|
@ -63,12 +67,10 @@ export default {
|
|||
font-weight: $font-weight-medium;
|
||||
margin-top: $space-smaller;
|
||||
margin-bottom: $space-smaller;
|
||||
color: $color-heading;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.body {
|
||||
color: $color-body;
|
||||
margin-bottom: $space-smaller;
|
||||
}
|
||||
|
||||
|
@ -77,10 +79,11 @@ export default {
|
|||
width: 100%;
|
||||
object-fit: contain;
|
||||
max-height: 150px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.action-button + .action-button {
|
||||
background: white;
|
||||
background: $color-white;
|
||||
@include thin-border($color-woot);
|
||||
color: $color-woot;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<div class="form chat-bubble agent">
|
||||
<div
|
||||
class="form chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<div
|
||||
v-for="item in items"
|
||||
|
@ -9,10 +12,13 @@
|
|||
'has-submitted': hasSubmitted,
|
||||
}"
|
||||
>
|
||||
<label>{{ item.label }}</label>
|
||||
<label :class="$dm('text-black-900', 'dark:text-slate-50')">{{
|
||||
item.label
|
||||
}}</label>
|
||||
<input
|
||||
v-if="item.type === 'email'"
|
||||
v-model="formValues[item.name]"
|
||||
:class="inputColor"
|
||||
:type="item.type"
|
||||
:pattern="item.regex"
|
||||
:title="item.title"
|
||||
|
@ -24,6 +30,7 @@
|
|||
<input
|
||||
v-else-if="item.type === 'text'"
|
||||
v-model="formValues[item.name]"
|
||||
:class="inputColor"
|
||||
:required="item.required && 'required'"
|
||||
:pattern="item.pattern"
|
||||
:title="item.title"
|
||||
|
@ -35,6 +42,7 @@
|
|||
<textarea
|
||||
v-else-if="item.type === 'text_area'"
|
||||
v-model="formValues[item.name]"
|
||||
:class="inputColor"
|
||||
:required="item.required && 'required'"
|
||||
:title="item.title"
|
||||
:name="item.name"
|
||||
|
@ -44,6 +52,7 @@
|
|||
<select
|
||||
v-else-if="item.type === 'select'"
|
||||
v-model="formValues[item.name]"
|
||||
:class="inputColor"
|
||||
:required="item.required && 'required'"
|
||||
>
|
||||
<option
|
||||
|
@ -73,7 +82,10 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
buttonLabel: {
|
||||
type: String,
|
||||
|
@ -98,6 +110,10 @@ export default {
|
|||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
},
|
||||
isFormValid() {
|
||||
return this.items.reduce((acc, { name }) => {
|
||||
return !!this.formValues[name] && acc;
|
||||
|
@ -186,7 +202,6 @@ export default {
|
|||
appearance: none;
|
||||
border: 1px solid $color-border;
|
||||
border-radius: $space-smaller;
|
||||
background-color: $color-white;
|
||||
font-family: inherit;
|
||||
font-size: $space-normal;
|
||||
font-weight: normal;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
|
@ -51,7 +52,6 @@ export default {
|
|||
background: transparent;
|
||||
border-radius: $space-large;
|
||||
border: 0;
|
||||
color: $color-woot;
|
||||
cursor: pointer;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="options-message chat-bubble agent">
|
||||
<div
|
||||
class="options-message chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h4 class="title">
|
||||
<h4 class="title" :class="$dm('text-black-900', 'dark:text-slate-50')">
|
||||
{{ title }}
|
||||
</h4>
|
||||
<ul
|
||||
|
@ -23,11 +26,13 @@
|
|||
|
||||
<script>
|
||||
import ChatOption from 'shared/components/ChatOption';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ChatOption,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
@ -80,7 +85,6 @@ export default {
|
|||
font-weight: $font-weight-normal;
|
||||
margin-top: $space-smaller;
|
||||
margin-bottom: $space-smaller;
|
||||
color: $color-heading;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<div class="customer-satisfcation" :style="{ borderColor: widgetColor }">
|
||||
<h6 class="title">
|
||||
<div
|
||||
class="customer-satisfaction"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
:style="{ borderColor: widgetColor }"
|
||||
>
|
||||
<h6 class="title" :class="$dm('text-slate-900', 'dark:text-slate-50')">
|
||||
{{ title }}
|
||||
</h6>
|
||||
<div class="ratings">
|
||||
|
@ -21,8 +25,9 @@
|
|||
<input
|
||||
v-model="feedback"
|
||||
class="form-input"
|
||||
:class="inputColor"
|
||||
:placeholder="$t('CSAT.PLACEHOLDER')"
|
||||
@keyup.enter="onSubmit"
|
||||
@keydown.enter="onSubmit"
|
||||
/>
|
||||
<button
|
||||
class="button small"
|
||||
|
@ -41,12 +46,14 @@ import { mapGetters } from 'vuex';
|
|||
import Spinner from 'shared/components/Spinner';
|
||||
import { CSAT_RATINGS } from 'shared/constants/messages';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Spinner,
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
messageContentAttributes: {
|
||||
type: Object,
|
||||
|
@ -67,9 +74,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
...mapGetters({ widgetColor: 'appConfig/getWidgetColor' }),
|
||||
isRatingSubmitted() {
|
||||
return this.messageContentAttributes?.csat_survey_response?.rating;
|
||||
},
|
||||
|
@ -80,6 +85,10 @@ export default {
|
|||
isButtonDisabled() {
|
||||
return !(this.selectedRating && this.feedback);
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
},
|
||||
title() {
|
||||
return this.isRatingSubmitted
|
||||
? this.$t('CSAT.SUBMITTED_TITLE')
|
||||
|
@ -136,10 +145,9 @@ export default {
|
|||
@import '~widget/assets/scss/variables.scss';
|
||||
@import '~widget/assets/scss/mixins.scss';
|
||||
|
||||
.customer-satisfcation {
|
||||
.customer-satisfaction {
|
||||
@include light-shadow;
|
||||
|
||||
background: $color-white;
|
||||
border-bottom-left-radius: $space-smaller;
|
||||
border-radius: $space-small;
|
||||
border-top: $space-micro solid $color-woot;
|
||||
|
@ -193,6 +201,10 @@ export default {
|
|||
border-top: 1px solid $color-border;
|
||||
padding: $space-one;
|
||||
width: 100%;
|
||||
|
||||
&::placeholder {
|
||||
color: $color-light-gray;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<template>
|
||||
<div class="date--separator">
|
||||
<div
|
||||
class="date--separator"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
>
|
||||
{{ formattedDate }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from 'shared/helpers/DateHelper';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
|
@ -30,7 +36,6 @@ export default {
|
|||
|
||||
.date--separator {
|
||||
font-size: $font-size-default;
|
||||
color: $color-body;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
position: relative;
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
<script>
|
||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
|
|
|
@ -1,17 +1,52 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import DateSeparator from '../DateSeparator';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
const localVue = createLocalVue();
|
||||
import i18n from 'dashboard/i18n';
|
||||
localVue.use(Vuex);
|
||||
localVue.use(VueI18n);
|
||||
|
||||
describe('DateSeparator', () => {
|
||||
test('matches snapshot', () => {
|
||||
const wrapper = mount(DateSeparator, {
|
||||
const i18nConfig = new VueI18n({
|
||||
locale: 'en',
|
||||
messages: i18n,
|
||||
});
|
||||
|
||||
describe('dateSeparator', () => {
|
||||
let store = null;
|
||||
let actions = null;
|
||||
let modules = null;
|
||||
let dateSeparator = null;
|
||||
|
||||
beforeEach(() => {
|
||||
actions = {};
|
||||
|
||||
modules = {
|
||||
auth: {
|
||||
getters: {
|
||||
'appConfig/darkMode': () => 'light',
|
||||
},
|
||||
},
|
||||
};
|
||||
store = new Vuex.Store({
|
||||
actions,
|
||||
modules,
|
||||
});
|
||||
|
||||
dateSeparator = shallowMount(DateSeparator, {
|
||||
store,
|
||||
localVue,
|
||||
propsData: {
|
||||
date: 'Nov 18, 2019',
|
||||
},
|
||||
mocks: {
|
||||
$t: () => {},
|
||||
},
|
||||
i18n: i18nConfig,
|
||||
mixins: [darkModeMixin],
|
||||
});
|
||||
expect(wrapper.vm).toBeTruthy();
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('date separator snapshot', () => {
|
||||
expect(dateSeparator.vm).toBeTruthy();
|
||||
expect(dateSeparator.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DateSeparator matches snapshot 1`] = `
|
||||
exports[`dateSeparator date separator snapshot 1`] = `
|
||||
<div
|
||||
class="date--separator"
|
||||
class="date--separator text-slate-700"
|
||||
>
|
||||
|
||||
Nov 18, 2019
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
}
|
||||
|
||||
.agent-name {
|
||||
color: $color-body;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-medium;
|
||||
margin: $space-small 0;
|
||||
|
@ -210,7 +209,6 @@
|
|||
|
||||
.chat-bubble {
|
||||
@include light-shadow;
|
||||
background: $color-woot;
|
||||
border-radius: $space-two;
|
||||
color: $color-white;
|
||||
display: inline-block;
|
||||
|
@ -242,7 +240,6 @@
|
|||
}
|
||||
|
||||
&.agent {
|
||||
background: $color-white;
|
||||
border-bottom-left-radius: $space-smaller;
|
||||
color: $color-body;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<div
|
||||
v-if="hasAttachments"
|
||||
class="chat-bubble has-attachment agent"
|
||||
:class="wrapClass"
|
||||
:class="(wrapClass, $dm('bg-white', 'dark:bg-slate-50'))"
|
||||
>
|
||||
<div v-for="attachment in message.attachments" :key="attachment.id">
|
||||
<image-bubble
|
||||
|
@ -40,7 +40,11 @@
|
|||
<file-bubble v-else :url="attachment.data_url" />
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="message.showAvatar || hasRecordedResponse" class="agent-name">
|
||||
<p
|
||||
v-if="message.showAvatar || hasRecordedResponse"
|
||||
class="agent-name"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
>
|
||||
{{ agentName }}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -68,6 +72,8 @@ import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
|||
import configMixin from '../mixins/configMixin';
|
||||
import messageMixin from '../mixins/messageMixin';
|
||||
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'AgentMessage',
|
||||
components: {
|
||||
|
@ -77,7 +83,7 @@ export default {
|
|||
UserMessage,
|
||||
FileBubble,
|
||||
},
|
||||
mixins: [timeMixin, configMixin, messageMixin],
|
||||
mixins: [timeMixin, configMixin, messageMixin, darkModeMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
|
|
|
@ -5,8 +5,13 @@
|
|||
!isCards && !isOptions && !isForm && !isArticle && !isCards && !isCSAT
|
||||
"
|
||||
class="chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<div class="message-content" v-html="formatMessage(message, false)"></div>
|
||||
<div
|
||||
class="message-content"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
v-html="formatMessage(message, false)"
|
||||
></div>
|
||||
<email-input
|
||||
v-if="isTemplateEmail"
|
||||
:message-id="messageId"
|
||||
|
@ -60,6 +65,7 @@ import ChatOptions from 'shared/components/ChatOptions';
|
|||
import ChatArticle from './template/Article';
|
||||
import EmailInput from './template/EmailInput';
|
||||
import CustomerSatisfaction from 'shared/components/CustomerSatisfaction';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'AgentMessageBubble',
|
||||
|
@ -71,7 +77,7 @@ export default {
|
|||
EmailInput,
|
||||
CustomerSatisfaction,
|
||||
},
|
||||
mixins: [messageFormatterMixin],
|
||||
mixins: [messageFormatterMixin, darkModeMixin],
|
||||
props: {
|
||||
message: { type: String, default: null },
|
||||
contentType: { type: String, default: null },
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
<div class="agent-message">
|
||||
<div class="avatar-wrap"></div>
|
||||
<div class="message-wrap">
|
||||
<div class="typing-bubble chat-bubble agent">
|
||||
<div
|
||||
class="typing-bubble chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-50')"
|
||||
>
|
||||
<img
|
||||
src="~widget/assets/images/typing.gif"
|
||||
alt="Agent is typing a message"
|
||||
|
@ -15,8 +18,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import darkModeMixing from 'widget/mixins/darkModeMixin.js';
|
||||
export default {
|
||||
name: 'AgentTypingBubble',
|
||||
mixins: [darkModeMixing],
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<template>
|
||||
<header class="flex justify-between p-5 w-full">
|
||||
<header
|
||||
class="flex justify-between p-5 w-full"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-900')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<button v-if="showBackButton" @click="onBackButtonClick">
|
||||
<fluent-icon icon="chevron-left" size="24" />
|
||||
<fluent-icon
|
||||
icon="chevron-left"
|
||||
size="24"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
<img
|
||||
v-if="avatarUrl"
|
||||
|
@ -11,7 +18,10 @@
|
|||
alt="avatar"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-black-900 font-medium text-base flex items-center">
|
||||
<div
|
||||
class="font-medium text-base flex items-center"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
>
|
||||
<span class="mr-1" v-html="title" />
|
||||
<div
|
||||
:class="
|
||||
|
@ -20,7 +30,10 @@
|
|||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs mt-1 text-black-700">
|
||||
<div
|
||||
class="text-xs mt-1"
|
||||
:class="$dm('text-black-700', 'dark:text-slate-400')"
|
||||
>
|
||||
{{ replyWaitMessage }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,6 +49,7 @@ import availabilityMixin from 'widget/mixins/availability';
|
|||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import HeaderActions from './HeaderActions';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import darkMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'ChatHeader',
|
||||
|
@ -43,7 +57,7 @@ export default {
|
|||
FluentIcon,
|
||||
HeaderActions,
|
||||
},
|
||||
mixins: [availabilityMixin, routerMixin],
|
||||
mixins: [availabilityMixin, routerMixin, darkMixin],
|
||||
props: {
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
|
@ -67,7 +81,9 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ widgetColor: 'appConfig/getWidgetColor' }),
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
isOnline() {
|
||||
const { workingHoursEnabled } = this.channelConfig;
|
||||
const anyAgentOnline = this.availableAgents.length > 0;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<header class="header-expanded bg-white py-6 px-5 relative box-border w-full">
|
||||
<header
|
||||
class="header-expanded py-6 px-5 relative box-border w-full"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-900')"
|
||||
>
|
||||
<div
|
||||
class="flex items-start"
|
||||
:class="[avatarUrl ? 'justify-between' : 'justify-end']"
|
||||
|
@ -8,21 +11,29 @@
|
|||
<header-actions :show-popout-button="showPopoutButton" />
|
||||
</div>
|
||||
<h2
|
||||
class="text-slate-900 mt-5 text-3xl mb-3 font-normal"
|
||||
class=" mt-5 text-3xl mb-3 font-normal"
|
||||
:class="$dm('text-slate-900', 'dark:text-slate-50')"
|
||||
v-html="introHeading"
|
||||
/>
|
||||
<p class="text-lg text-black-700 leading-normal" v-html="introBody" />
|
||||
<p
|
||||
class="text-lg leading-normal"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
v-html="introBody"
|
||||
/>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import HeaderActions from './HeaderActions';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'ChatHeaderExpanded',
|
||||
components: {
|
||||
HeaderActions,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="chat-message--input"
|
||||
:class="{ 'is-focused': isFocused }"
|
||||
class="chat-message--input is-focused"
|
||||
:class="$dm('bg-white ', 'dark:bg-slate-600')"
|
||||
@keydown.esc="hideEmojiPicker"
|
||||
>
|
||||
<resizable-text-area
|
||||
|
@ -10,7 +10,8 @@
|
|||
v-model="userInput"
|
||||
:aria-label="$t('CHAT_PLACEHOLDER')"
|
||||
:placeholder="$t('CHAT_PLACEHOLDER')"
|
||||
class="form-input user-message-input"
|
||||
class="form-input user-message-input is-focused"
|
||||
:class="inputColor"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
|
@ -19,6 +20,7 @@
|
|||
<div class="button-wrap">
|
||||
<chat-attachment-button
|
||||
v-if="showAttachment"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-100')"
|
||||
:on-attach="onSendAttachment"
|
||||
/>
|
||||
<button
|
||||
|
@ -27,10 +29,7 @@
|
|||
aria-label="Emoji picker"
|
||||
@click="toggleEmojiPicker"
|
||||
>
|
||||
<fluent-icon
|
||||
icon="emoji"
|
||||
:class="{ 'text-woot-500': showEmojiPicker }"
|
||||
/>
|
||||
<fluent-icon icon="emoji" :class="emojiIconColor" />
|
||||
</button>
|
||||
<emoji-input
|
||||
v-if="showEmojiPicker"
|
||||
|
@ -57,6 +56,7 @@ import configMixin from '../mixins/configMixin';
|
|||
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'ChatInputWrap',
|
||||
|
@ -67,7 +67,7 @@ export default {
|
|||
FluentIcon,
|
||||
ResizableTextArea,
|
||||
},
|
||||
mixins: [clickaway, configMixin],
|
||||
mixins: [clickaway, configMixin, darkModeMixin],
|
||||
props: {
|
||||
onSendMessage: {
|
||||
type: Function,
|
||||
|
@ -98,6 +98,15 @@ export default {
|
|||
showSendButton() {
|
||||
return this.userInput.length > 0;
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
},
|
||||
emojiIconColor() {
|
||||
return this.showEmojiPicker
|
||||
? `text-woot-500 ${this.$dm('text-black-900', 'dark:text-slate-100')}`
|
||||
: `${this.$dm('text-black-900', 'dark:text-slate-100')}`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isWidgetOpen(isWidgetOpen) {
|
||||
|
|
|
@ -1,22 +1,12 @@
|
|||
<template>
|
||||
<label class="block">
|
||||
<div
|
||||
v-if="label"
|
||||
class="mb-2 text-xs font-medium"
|
||||
:class="{
|
||||
'text-black-800': !error,
|
||||
'text-red-400': error,
|
||||
}"
|
||||
>
|
||||
<div v-if="label" class="mb-2 text-xs font-medium" :class="labelClass">
|
||||
{{ label }}
|
||||
</div>
|
||||
<input
|
||||
:type="type"
|
||||
class="border rounded w-full py-2 px-3 text-slate-700 leading-tight outline-none"
|
||||
:class="{
|
||||
'border-black-200 hover:border-black-300 focus:border-black-300': !error,
|
||||
'border-red-200 hover:border-red-300 focus:border-red-300': error,
|
||||
}"
|
||||
class="border rounded w-full py-2 px-3 leading-tight outline-none"
|
||||
:class="inputHasError"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
|
@ -27,7 +17,9 @@
|
|||
</label>
|
||||
</template>
|
||||
<script>
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
|
@ -50,6 +42,27 @@ export default {
|
|||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelClass() {
|
||||
return this.error
|
||||
? `text-red-400 ${this.$dm('text-black-800', 'dark:text-slate-50')}`
|
||||
: `text-black-800 ${this.$dm('text-black-800', 'dark:text-slate-50')}`;
|
||||
},
|
||||
isInputDarkOrLightMode() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')} ${this.$dm(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
},
|
||||
inputBorderColor() {
|
||||
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
},
|
||||
inputHasError() {
|
||||
return this.error
|
||||
? `border-red-200 hover:border-red-300 focus:border-red-300 ${this.isInputDarkOrLightMode}`
|
||||
: `hover:border-black-300 focus:border-black-300 ${this.isInputDarkOrLightMode} ${this.inputBorderColor}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(event) {
|
||||
this.$emit('input', event.target.value);
|
||||
|
|
|
@ -1,21 +1,11 @@
|
|||
<template>
|
||||
<label class="block">
|
||||
<div
|
||||
v-if="label"
|
||||
class="mb-2 text-xs font-medium"
|
||||
:class="{
|
||||
'text-black-800': !error,
|
||||
'text-red-400': error,
|
||||
}"
|
||||
>
|
||||
<div v-if="label" class="mb-2 text-xs font-medium" :class="labelClass">
|
||||
{{ label }}
|
||||
</div>
|
||||
<textarea
|
||||
class="resize-none border rounded w-full py-2 px-3 text-slate-700 leading-tight outline-none"
|
||||
:class="{
|
||||
'border-black-200 hover:border-black-300 focus:border-black-300': !error,
|
||||
'border-red-200 hover:border-red-300 focus:border-red-300': error,
|
||||
}"
|
||||
:class="isTextAreaHasError"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
|
@ -26,7 +16,9 @@
|
|||
</label>
|
||||
</template>
|
||||
<script>
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
|
@ -49,6 +41,27 @@ export default {
|
|||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
labelClass() {
|
||||
return this.error
|
||||
? `text-red-400 ${this.$dm('text-black-800', 'dark:text-slate-50')}`
|
||||
: `text-black-800 ${this.$dm('text-black-800', 'dark:text-slate-50')}`;
|
||||
},
|
||||
isTextAreaDarkOrLightMode() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')} ${this.$dm(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
},
|
||||
textAreaBorderColor() {
|
||||
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
},
|
||||
isTextAreaHasError() {
|
||||
return this.error
|
||||
? `border-red-200 hover:border-red-300 focus:border-red-300 ${this.isTextAreaDarkOrLightMode}`
|
||||
: `hover:border-black-300 focus:border-black-300 ${this.isTextAreaDarkOrLightMode} ${this.textAreaBorderColor}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onChange(event) {
|
||||
this.$emit('input', event.target.value);
|
||||
|
|
|
@ -6,14 +6,22 @@
|
|||
:title="$t('END_CONVERSATION')"
|
||||
@click="resolveConversation"
|
||||
>
|
||||
<fluent-icon icon="sign-out" size="22" class="text-black-900" />
|
||||
<fluent-icon
|
||||
icon="sign-out"
|
||||
size="22"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="showPopoutButton"
|
||||
class="button transparent compact new-window--button "
|
||||
@click="popoutWindow"
|
||||
>
|
||||
<fluent-icon icon="open" size="22" class="text-black-900" />
|
||||
<fluent-icon
|
||||
icon="open"
|
||||
size="22"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="button transparent compact close-button"
|
||||
|
@ -22,7 +30,11 @@
|
|||
}"
|
||||
@click="closeWindow"
|
||||
>
|
||||
<fluent-icon icon="dismiss" size="24" class="text-black-900" />
|
||||
<fluent-icon
|
||||
icon="dismiss"
|
||||
size="24"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -31,10 +43,12 @@ import { mapGetters } from 'vuex';
|
|||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||
import { popoutChatWindow } from '../helpers/popoutHelper';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'HeaderActions',
|
||||
components: { FluentIcon },
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
showPopoutButton: {
|
||||
type: Boolean,
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
>
|
||||
<div
|
||||
v-if="shouldShowHeaderMessage"
|
||||
class="text-black-800 text-sm leading-5"
|
||||
class="text-sm leading-5"
|
||||
:class="$dm('text-black-800', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ headerMessage }}
|
||||
</div>
|
||||
|
@ -64,6 +65,7 @@ import { getContrastingTextColor } from '@chatwoot/utils';
|
|||
import { required, minLength, email } from 'vuelidate/lib/validators';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
export default {
|
||||
components: {
|
||||
FormInput,
|
||||
|
@ -71,7 +73,7 @@ export default {
|
|||
CustomButton,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [routerMixin],
|
||||
mixins: [routerMixin, darkModeMixin],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="px-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-black-700 max-w-xs">
|
||||
<div
|
||||
class="max-w-xs"
|
||||
:class="$dm('text-black-700', 'dark:text-slate-50')"
|
||||
>
|
||||
<div class="text-base leading-5 font-medium mb-1">
|
||||
{{
|
||||
isOnline
|
||||
|
@ -36,6 +39,7 @@ import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
|||
import CustomButton from 'shared/components/Button';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import darkMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
name: 'TeamAvailability',
|
||||
|
@ -43,7 +47,7 @@ export default {
|
|||
AvailableAgents,
|
||||
CustomButton,
|
||||
},
|
||||
mixins: [configMixin, availabilityMixin],
|
||||
mixins: [configMixin, availabilityMixin, darkMixin],
|
||||
props: {
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
|
@ -55,7 +59,9 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ widgetColor: 'appConfig/getWidgetColor' }),
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
textColor() {
|
||||
return getContrastingTextColor(this.widgetColor);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<div class="chat-bubble-wrap">
|
||||
<button class="chat-bubble agent" @click="onClickMessage">
|
||||
<button
|
||||
class="chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-50')"
|
||||
@click="onClickMessage"
|
||||
>
|
||||
<div v-if="showSender" class="row--agent-block">
|
||||
<thumbnail
|
||||
:src="avatarUrl"
|
||||
|
@ -25,10 +29,11 @@ import {
|
|||
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||
ON_UNREAD_MESSAGE_CLICK,
|
||||
} from '../constants/widgetBusEvents';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
export default {
|
||||
name: 'UnreadMessage',
|
||||
components: { Thumbnail },
|
||||
mixins: [messageFormatterMixin, configMixin],
|
||||
mixins: [messageFormatterMixin, configMixin, darkModeMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<template>
|
||||
<div
|
||||
class="w-full h-full bg-slate-50 flex flex-col"
|
||||
class="w-full h-full flex flex-col"
|
||||
:class="$dm('bg-slate-50', 'dark:bg-slate-800')"
|
||||
@keydown.esc="closeWindow"
|
||||
>
|
||||
<div
|
||||
class="header-wrap bg-white"
|
||||
:class="{ expanded: !isHeaderCollapsed, collapsed: isHeaderCollapsed }"
|
||||
class="header-wrap"
|
||||
:class="{
|
||||
expanded: !isHeaderCollapsed,
|
||||
collapsed: isHeaderCollapsed,
|
||||
}"
|
||||
>
|
||||
<transition
|
||||
enter-active-class="transition-all delay-200 duration-300 ease-in"
|
||||
|
@ -51,6 +55,7 @@ import Branding from 'shared/components/Branding.vue';
|
|||
import ChatHeader from '../ChatHeader.vue';
|
||||
import ChatHeaderExpanded from '../ChatHeaderExpanded.vue';
|
||||
import configMixin from '../../mixins/configMixin';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
|
||||
|
@ -61,7 +66,7 @@ export default {
|
|||
ChatHeader,
|
||||
ChatHeaderExpanded,
|
||||
},
|
||||
mixins: [configMixin],
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
data() {
|
||||
return {
|
||||
showPopoutButton: false,
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
<template>
|
||||
<div v-if="!!items.length" class="chat-bubble agent">
|
||||
<div
|
||||
v-if="!!items.length"
|
||||
class="chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<div v-for="item in items" :key="item.link" class="article-item">
|
||||
<a :href="item.link" target="_blank" rel="noopener noreferrer nofollow">
|
||||
<span class="title flex items-center text-black-900 font-medium">
|
||||
<fluent-icon icon="link" class="mr-1" />
|
||||
<span>{{ item.title }}</span>
|
||||
<fluent-icon
|
||||
icon="link"
|
||||
class="mr-1"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
<span :class="$dm('text-slate-900', 'dark:text-slate-50')">{{
|
||||
item.title
|
||||
}}</span>
|
||||
</span>
|
||||
<span class="description">
|
||||
<span
|
||||
class="description"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
>
|
||||
{{ truncateMessage(item.description) }}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -17,12 +30,13 @@
|
|||
<script>
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [messageFormatterMixin],
|
||||
mixins: [messageFormatterMixin, darkModeMixin],
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
v-model.trim="email"
|
||||
class="form-input"
|
||||
:placeholder="$t('EMAIL_PLACEHOLDER')"
|
||||
:class="{ error: $v.email.$error }"
|
||||
:class="inputHasError"
|
||||
@input="$v.email.$touch"
|
||||
@keydown.enter="onSubmit"
|
||||
/>
|
||||
|
@ -31,12 +31,14 @@ import { required, email } from 'vuelidate/lib/validators';
|
|||
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
messageId: {
|
||||
type: Number,
|
||||
|
@ -63,6 +65,15 @@ export default {
|
|||
this.messageContentAttributes.submitted_email
|
||||
);
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
},
|
||||
inputHasError() {
|
||||
return this.$v.email.$error
|
||||
? `${this.inputColor} error`
|
||||
: `${this.inputColor}`;
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
email: {
|
||||
|
@ -105,6 +116,10 @@ export default {
|
|||
padding: $space-one;
|
||||
width: 100%;
|
||||
|
||||
&::placeholder {
|
||||
color: $color-light-gray;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: $color-error;
|
||||
}
|
||||
|
|
15
app/javascript/widget/mixins/darkModeMixin.js
Normal file
15
app/javascript/widget/mixins/darkModeMixin.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters({ darkMode: 'appConfig/darkMode' }),
|
||||
},
|
||||
methods: {
|
||||
$dm(light, dark) {
|
||||
if (this.darkMode === 'light') {
|
||||
return light;
|
||||
}
|
||||
return light + ' ' + dark;
|
||||
},
|
||||
},
|
||||
};
|
41
app/javascript/widget/mixins/specs/darkModeMixin.spec.js
Normal file
41
app/javascript/widget/mixins/specs/darkModeMixin.spec.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import darkModeMixin from '../darkModeMixin';
|
||||
import Vuex from 'vuex';
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
const darkModeValues = ['light', 'auto'];
|
||||
|
||||
describe('darkModeMixin', () => {
|
||||
let getters;
|
||||
let store;
|
||||
beforeEach(() => {
|
||||
getters = {
|
||||
'appConfig/darkMode': () => darkModeValues[0],
|
||||
};
|
||||
store = new Vuex.Store({ getters });
|
||||
});
|
||||
|
||||
it('if light theme', () => {
|
||||
const Component = {
|
||||
render() {},
|
||||
mixins: [darkModeMixin],
|
||||
};
|
||||
const wrapper = shallowMount(Component, { store, localVue });
|
||||
expect(wrapper.vm.$dm('bg-100', 'bg-600')).toBe('bg-100');
|
||||
});
|
||||
|
||||
it('if auto theme', () => {
|
||||
getters = {
|
||||
'appConfig/darkMode': () => darkModeValues[2],
|
||||
};
|
||||
store = new Vuex.Store({ getters });
|
||||
|
||||
const Component = {
|
||||
render() {},
|
||||
mixins: [darkModeMixin],
|
||||
};
|
||||
const wrapper = shallowMount(Component, { store, localVue });
|
||||
expect(wrapper.vm.$dm('bg-100', 'bg-600')).toBe('bg-100 bg-600');
|
||||
});
|
||||
});
|
|
@ -15,6 +15,7 @@ const state = {
|
|||
showPopoutButton: false,
|
||||
widgetColor: '',
|
||||
widgetStyle: 'standard',
|
||||
darkMode: 'light',
|
||||
};
|
||||
|
||||
export const getters = {
|
||||
|
@ -25,18 +26,26 @@ export const getters = {
|
|||
getWidgetColor: $state => $state.widgetColor,
|
||||
getReferrerHost: $state => $state.referrerHost,
|
||||
isWidgetStyleFlat: $state => $state.widgetStyle === 'flat',
|
||||
darkMode: $state => $state.darkMode,
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
setAppConfig(
|
||||
{ commit },
|
||||
{ showPopoutButton, position, hideMessageBubble, widgetStyle = 'rounded' }
|
||||
{
|
||||
showPopoutButton,
|
||||
position,
|
||||
hideMessageBubble,
|
||||
widgetStyle = 'rounded',
|
||||
darkMode = 'light',
|
||||
}
|
||||
) {
|
||||
commit(SET_WIDGET_APP_CONFIG, {
|
||||
hideMessageBubble: !!hideMessageBubble,
|
||||
position: position || 'right',
|
||||
showPopoutButton: !!showPopoutButton,
|
||||
widgetStyle,
|
||||
darkMode,
|
||||
});
|
||||
},
|
||||
toggleWidgetOpen({ commit }, isWidgetOpen) {
|
||||
|
@ -56,6 +65,7 @@ export const mutations = {
|
|||
$state.position = data.position;
|
||||
$state.hideMessageBubble = data.hideMessageBubble;
|
||||
$state.widgetStyle = data.widgetStyle;
|
||||
$state.darkMode = data.darkMode;
|
||||
},
|
||||
[TOGGLE_WIDGET_OPEN]($state, isWidgetOpen) {
|
||||
$state.isWidgetOpen = isWidgetOpen;
|
||||
|
|
|
@ -19,6 +19,7 @@ window.chatwootSettings = {
|
|||
type: '<%= @widget_type %>',
|
||||
showPopoutButton: true,
|
||||
widgetStyle: '<%= @widget_style %>',
|
||||
darkMode: '<%= @dark_mode %>',
|
||||
};
|
||||
|
||||
(function(d,t) {
|
||||
|
|
|
@ -85,7 +85,11 @@ module.exports = {
|
|||
900: '#C30011',
|
||||
},
|
||||
},
|
||||
extend: {},
|
||||
extend: {
|
||||
screens: {
|
||||
dark: { raw: '(prefers-color-scheme: dark)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {},
|
||||
plugins: [],
|
||||
|
|
Loading…
Reference in a new issue