mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +00:00
chore: Replace darkmode mixin with useDarkMode composable [CW-3474] (#9949)
# Pull Request Template ## Description Replaces darkModeMixin with the new useDarkMode composable and replaces wll usages of mixin the the composable in components and pages Fixes https://linear.app/chatwoot/issue/CW-3474/rewrite-darkmodemixin-mixin-to-a-composable ## Type of change Please delete options that are not relevant. - [x] New feature (non-breaking change which adds functionality) --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
<script>
|
||||
import CardButton from 'shared/components/CardButton.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardButton,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
@@ -25,21 +24,30 @@ export default {
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="card-message chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<img class="media" :src="mediaUrl" />
|
||||
<div class="card-body">
|
||||
<h4 class="title" :class="$dm('text-black-900', 'dark:text-slate-50')">
|
||||
<h4
|
||||
class="title"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ title }}
|
||||
</h4>
|
||||
<p class="body" :class="$dm('text-black-700', 'dark:text-slate-100')">
|
||||
<p
|
||||
class="body"
|
||||
:class="getThemeClass('text-black-700', 'dark:text-slate-100')"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
<CardButton v-for="action in actions" :key="action.id" :action="action" />
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
buttonLabel: {
|
||||
type: String,
|
||||
@@ -19,6 +18,10 @@ export default {
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formValues: {},
|
||||
@@ -33,8 +36,8 @@ export default {
|
||||
return getContrastingTextColor(this.widgetColor);
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
return `${this.getThemeClass('bg-white', 'dark:bg-slate-600')}
|
||||
${this.getThemeClass('text-black-900', 'dark:text-slate-50')}`;
|
||||
},
|
||||
isFormValid() {
|
||||
return this.items.reduce((acc, { name }) => {
|
||||
@@ -80,7 +83,7 @@ export default {
|
||||
<template>
|
||||
<div
|
||||
class="form chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<div
|
||||
@@ -91,7 +94,7 @@ export default {
|
||||
'has-submitted': hasSubmitted,
|
||||
}"
|
||||
>
|
||||
<label :class="$dm('text-black-900', 'dark:text-slate-50')">{{
|
||||
<label :class="getThemeClass('text-black-900', 'dark:text-slate-50')">{{
|
||||
item.label
|
||||
}}</label>
|
||||
<input
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mapGetters } from 'vuex';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import { CSAT_RATINGS } from 'shared/constants/messages';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
|
||||
export default {
|
||||
@@ -11,7 +11,6 @@ export default {
|
||||
Spinner,
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
messageContentAttributes: {
|
||||
type: Object,
|
||||
@@ -22,6 +21,10 @@ export default {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
@@ -44,8 +47,8 @@ export default {
|
||||
return !(this.selectedRating && this.feedback);
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
return `${this.getThemeClass('bg-white', 'dark:bg-slate-600')}
|
||||
${this.getThemeClass('text-black-900', 'dark:text-slate-50')}`;
|
||||
},
|
||||
textColor() {
|
||||
return getContrastingTextColor(this.widgetColor);
|
||||
@@ -105,10 +108,13 @@ export default {
|
||||
<template>
|
||||
<div
|
||||
class="customer-satisfaction"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-700')"
|
||||
:style="{ borderColor: widgetColor }"
|
||||
>
|
||||
<h6 class="title" :class="$dm('text-slate-900', 'dark:text-slate-50')">
|
||||
<h6
|
||||
class="title"
|
||||
:class="getThemeClass('text-slate-900', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ title }}
|
||||
</h6>
|
||||
<div class="ratings">
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<script>
|
||||
import { formatDate } from 'shared/helpers/DateHelper';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
computed: {
|
||||
formattedDate() {
|
||||
return formatDate({
|
||||
@@ -25,7 +28,7 @@ export default {
|
||||
<template>
|
||||
<div
|
||||
class="date--separator"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-200')"
|
||||
>
|
||||
{{ formattedDate }}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import DateSeparator from '../DateSeparator.vue';
|
||||
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);
|
||||
@@ -40,7 +39,6 @@ describe('dateSeparator', () => {
|
||||
propsData: { date: 'Nov 18, 2019' },
|
||||
mocks: { $t: msg => msg },
|
||||
i18n: i18nConfig,
|
||||
mixins: [darkModeMixin],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||
ON_UNREAD_MESSAGE_CLICK,
|
||||
} from './constants/widgetBusEvents';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import { SDK_SET_BUBBLE_VISIBILITY } from '../shared/constants/sharedFrameEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
@@ -27,7 +27,11 @@ export default {
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
mixins: [availabilityMixin, configMixin, routerMixin, darkModeMixin],
|
||||
mixins: [availabilityMixin, configMixin, routerMixin],
|
||||
setup() {
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
return { prefersDarkMode };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMobile: false,
|
||||
|
||||
@@ -11,7 +11,7 @@ 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';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import ReplyToChip from 'widget/components/ReplyToChip.vue';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
MessageReplyButton,
|
||||
ReplyToChip,
|
||||
},
|
||||
mixins: [configMixin, messageMixin, darkModeMixin],
|
||||
mixins: [configMixin, messageMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
@@ -39,6 +39,12 @@ export default {
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return {
|
||||
getThemeClass,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasImageError: false,
|
||||
@@ -192,7 +198,9 @@ export default {
|
||||
<div
|
||||
v-if="hasAttachments"
|
||||
class="space-y-2 chat-bubble has-attachment agent"
|
||||
:class="(wrapClass, $dm('bg-white', 'dark:bg-slate-700'))"
|
||||
:class="
|
||||
(wrapClass, getThemeClass('bg-white', 'dark:bg-slate-700'))
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-for="attachment in message.attachments"
|
||||
@@ -231,7 +239,7 @@ export default {
|
||||
v-if="message.showAvatar || hasRecordedResponse"
|
||||
v-dompurify-html="agentName"
|
||||
class="agent-name"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-200')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import ChatOptions from 'shared/components/ChatOptions.vue';
|
||||
import ChatArticle from './template/Article.vue';
|
||||
import EmailInput from './template/EmailInput.vue';
|
||||
import CustomerSatisfaction from 'shared/components/CustomerSatisfaction.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import IntegrationCard from './template/IntegrationCard.vue';
|
||||
|
||||
export default {
|
||||
@@ -20,7 +20,6 @@ export default {
|
||||
CustomerSatisfaction,
|
||||
IntegrationCard,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
message: { type: String, default: null },
|
||||
contentType: { type: String, default: null },
|
||||
@@ -34,11 +33,13 @@ export default {
|
||||
setup() {
|
||||
const { formatMessage, getPlainText, truncateMessage, highlightContent } =
|
||||
useMessageFormatter();
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return {
|
||||
formatMessage,
|
||||
getPlainText,
|
||||
truncateMessage,
|
||||
highlightContent,
|
||||
getThemeClass,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -98,7 +99,7 @@ export default {
|
||||
!isCards && !isOptions && !isForm && !isArticle && !isCards && !isCSAT
|
||||
"
|
||||
class="chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700 has-dark-mode')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-700 has-dark-mode')"
|
||||
>
|
||||
<div
|
||||
v-dompurify-html="formatMessage(message, false)"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script>
|
||||
import darkModeMixing from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
export default {
|
||||
name: 'AgentTypingBubble',
|
||||
mixins: [darkModeMixing],
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -13,7 +16,7 @@ export default {
|
||||
<div class="message-wrap mt-2">
|
||||
<div
|
||||
class="typing-bubble chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<img
|
||||
src="~widget/assets/images/typing.gif"
|
||||
|
||||
@@ -4,7 +4,7 @@ import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import HeaderActions from './HeaderActions.vue';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import darkMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
name: 'ChatHeader',
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
FluentIcon,
|
||||
HeaderActions,
|
||||
},
|
||||
mixins: [nextAvailabilityTime, availabilityMixin, routerMixin, darkMixin],
|
||||
mixins: [nextAvailabilityTime, availabilityMixin, routerMixin],
|
||||
props: {
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
@@ -35,6 +35,10 @@ export default {
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
computed: {
|
||||
isOnline() {
|
||||
const { workingHoursEnabled } = this.channelConfig;
|
||||
@@ -57,7 +61,7 @@ export default {
|
||||
<template>
|
||||
<header
|
||||
class="flex justify-between w-full p-5"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-900')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-900')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
@@ -68,7 +72,7 @@ export default {
|
||||
<FluentIcon
|
||||
icon="chevron-left"
|
||||
size="24"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
<img
|
||||
@@ -80,7 +84,7 @@ export default {
|
||||
<div>
|
||||
<div
|
||||
class="flex items-center text-base font-medium leading-4"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
>
|
||||
<span v-dompurify-html="title" class="mr-1" />
|
||||
<div
|
||||
@@ -90,7 +94,7 @@ export default {
|
||||
</div>
|
||||
<div
|
||||
class="mt-1 text-xs leading-3"
|
||||
:class="$dm('text-black-700', 'dark:text-slate-400')"
|
||||
:class="getThemeClass('text-black-700', 'dark:text-slate-400')"
|
||||
>
|
||||
{{ replyWaitMessage }}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script>
|
||||
import HeaderActions from './HeaderActions.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
name: 'ChatHeaderExpanded',
|
||||
components: {
|
||||
HeaderActions,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
avatarUrl: {
|
||||
type: String,
|
||||
@@ -26,6 +25,10 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -51,12 +54,12 @@ export default {
|
||||
<h2
|
||||
v-dompurify-html="introHeading"
|
||||
class="mt-4 text-2xl mb-1.5 font-medium"
|
||||
:class="$dm('text-slate-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-slate-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
<p
|
||||
v-dompurify-html="introBody"
|
||||
class="text-base leading-normal"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-200')"
|
||||
/>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -6,7 +6,7 @@ import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
|
||||
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
FluentIcon,
|
||||
ResizableTextArea,
|
||||
},
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
onSendMessage: {
|
||||
type: Function,
|
||||
@@ -30,7 +30,10 @@ export default {
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userInput: '',
|
||||
@@ -51,13 +54,16 @@ export default {
|
||||
return this.userInput.length > 0;
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}`;
|
||||
return `${this.getThemeClass('bg-white', 'dark:bg-slate-600')}
|
||||
${this.getThemeClass('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')}`;
|
||||
? `text-woot-500 ${this.getThemeClass(
|
||||
'text-black-900',
|
||||
'dark:text-slate-100'
|
||||
)}`
|
||||
: `${this.getThemeClass('text-black-900', 'dark:text-slate-100')}`;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -128,7 +134,7 @@ export default {
|
||||
<template>
|
||||
<div
|
||||
class="chat-message--input is-focused"
|
||||
:class="$dm('bg-white ', 'dark:bg-slate-600')"
|
||||
:class="getThemeClass('bg-white ', 'dark:bg-slate-600')"
|
||||
@keydown.esc="hideEmojiPicker"
|
||||
>
|
||||
<ResizableTextArea
|
||||
@@ -148,7 +154,7 @@ export default {
|
||||
<div class="button-wrap">
|
||||
<ChatAttachmentButton
|
||||
v-if="showAttachment"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-100')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-100')"
|
||||
:on-attach="onSendAttachment"
|
||||
/>
|
||||
<button
|
||||
|
||||
@@ -3,7 +3,7 @@ import ChatMessage from 'widget/components/ChatMessage.vue';
|
||||
import AgentTypingBubble from 'widget/components/AgentTypingBubble.vue';
|
||||
import DateSeparator from 'shared/components/DateSeparator.vue';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
@@ -15,13 +15,16 @@ export default {
|
||||
DateSeparator,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
groupedMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { darkMode } = useDarkMode();
|
||||
return { darkMode };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
previousScrollHeight: 0,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script>
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
@@ -26,6 +25,10 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
return this.isInProgress
|
||||
@@ -45,7 +48,7 @@ export default {
|
||||
},
|
||||
titleColor() {
|
||||
return !this.isUserBubble
|
||||
? this.$dm('text-black-900', 'dark:text-slate-50')
|
||||
? this.getThemeClass('text-black-900', 'dark:text-slate-50')
|
||||
: '';
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
@@ -28,20 +27,33 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
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')}`;
|
||||
? `text-red-400 ${this.getThemeClass(
|
||||
'text-black-800',
|
||||
'dark:text-slate-50'
|
||||
)}`
|
||||
: `text-black-800 ${this.getThemeClass(
|
||||
'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'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-600'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
inputBorderColor() {
|
||||
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
return `${this.getThemeClass(
|
||||
'border-black-200',
|
||||
'dark:border-black-500'
|
||||
)}`;
|
||||
},
|
||||
inputHasError() {
|
||||
return this.error
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import FormulateInputMixin from '@braid/vue-formulate/src/FormulateInputMixin';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [FormulateInputMixin, darkModeMixin],
|
||||
mixins: [FormulateInputMixin],
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
@@ -19,6 +19,10 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: -1,
|
||||
@@ -45,28 +49,31 @@ export default {
|
||||
return this.activeCountryCode ? 'Clear selection' : 'Select Country';
|
||||
},
|
||||
dropdownClass() {
|
||||
return `${this.$dm('bg-slate-100', 'dark:bg-slate-700')} ${this.$dm(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'bg-slate-100',
|
||||
'dark:bg-slate-700'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
dropdownBackgroundClass() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-700')} ${this.$dm(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-700'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
dropdownItemClass() {
|
||||
return `${this.$dm('text-slate-700', 'dark:text-slate-50')} ${this.$dm(
|
||||
'hover:bg-slate-50',
|
||||
'dark:hover:bg-slate-600'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)} ${this.getThemeClass('hover:bg-slate-50', 'dark:hover:bg-slate-600')}`;
|
||||
},
|
||||
activeDropdownItemClass() {
|
||||
return `active ${this.$dm('bg-slate-100', 'dark:bg-slate-800')}`;
|
||||
return `active ${this.getThemeClass(
|
||||
'bg-slate-100',
|
||||
'dark:bg-slate-800'
|
||||
)}`;
|
||||
},
|
||||
focusedDropdownItemClass() {
|
||||
return `focus ${this.$dm('bg-slate-50', 'dark:bg-slate-600')}`;
|
||||
return `focus ${this.getThemeClass('bg-slate-50', 'dark:bg-slate-600')}`;
|
||||
},
|
||||
inputHasError() {
|
||||
return this.hasErrorInPhoneInput
|
||||
@@ -74,13 +81,16 @@ export default {
|
||||
: `hover:border-black-300 focus:border-black-300 ${this.inputLightAndDarkModeColor} ${this.inputBorderColor}`;
|
||||
},
|
||||
inputBorderColor() {
|
||||
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
return `${this.getThemeClass(
|
||||
'border-black-200',
|
||||
'dark:border-black-500'
|
||||
)}`;
|
||||
},
|
||||
inputLightAndDarkModeColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')} ${this.$dm(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-600'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
items() {
|
||||
return this.countries.filter(country => {
|
||||
@@ -241,7 +251,7 @@ export default {
|
||||
<span
|
||||
v-if="activeDialCode"
|
||||
class="py-2 pl-2 pr-0 text-base"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ activeDialCode }}
|
||||
</span>
|
||||
@@ -273,7 +283,10 @@ export default {
|
||||
type="text"
|
||||
:placeholder="$t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.DROPDOWN_SEARCH')"
|
||||
class="w-full h-8 px-3 py-2 mt-1 mb-1 text-sm border border-solid rounded outline-none dropdown-search"
|
||||
:class="[$dm('bg-slate-50', 'dark:bg-slate-600'), inputBorderColor]"
|
||||
:class="[
|
||||
getThemeClass('bg-slate-50', 'dark:bg-slate-600'),
|
||||
inputBorderColor,
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -298,7 +311,7 @@ export default {
|
||||
<div v-if="items.length === 0">
|
||||
<span
|
||||
class="flex justify-center mt-4 text-sm text-center"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ $t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.DROPDOWN_EMPTY') }}
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
export default {
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
@@ -20,20 +19,33 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
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')}`;
|
||||
? `text-red-400 ${this.getThemeClass(
|
||||
'text-black-800',
|
||||
'dark:text-slate-50'
|
||||
)}`
|
||||
: `text-black-800 ${this.getThemeClass(
|
||||
'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'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-600'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
textAreaBorderColor() {
|
||||
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
return `${this.getThemeClass(
|
||||
'border-black-200',
|
||||
'dark:border-black-500'
|
||||
)}`;
|
||||
},
|
||||
isTextAreaHasError() {
|
||||
return this.error
|
||||
|
||||
@@ -3,14 +3,14 @@ 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';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import { CONVERSATION_STATUS } from 'shared/constants/messages';
|
||||
|
||||
export default {
|
||||
name: 'HeaderActions',
|
||||
components: { FluentIcon },
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
showPopoutButton: {
|
||||
type: Boolean,
|
||||
@@ -21,6 +21,10 @@ export default {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
conversationAttributes: 'conversationAttributes/getConversationParams',
|
||||
@@ -92,7 +96,7 @@ export default {
|
||||
<FluentIcon
|
||||
icon="sign-out"
|
||||
size="22"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
@@ -103,7 +107,7 @@ export default {
|
||||
<FluentIcon
|
||||
icon="open"
|
||||
size="22"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
@@ -116,7 +120,7 @@ export default {
|
||||
<FluentIcon
|
||||
icon="dismiss"
|
||||
size="24"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import { getRegexp } from 'shared/helpers/Validators';
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
|
||||
export default {
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
CustomButton,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [routerMixin, darkModeMixin, configMixin],
|
||||
mixins: [routerMixin, configMixin],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
@@ -24,9 +24,8 @@ export default {
|
||||
},
|
||||
setup() {
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
return {
|
||||
formatMessage,
|
||||
};
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { formatMessage, getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -131,25 +130,28 @@ export default {
|
||||
return `mt-1 border rounded w-full py-2 px-3 text-slate-700 outline-none`;
|
||||
},
|
||||
isInputDarkOrLightMode() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')} ${this.$dm(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-600'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
inputBorderColor() {
|
||||
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
return `${this.getThemeClass(
|
||||
'border-black-200',
|
||||
'dark:border-black-500'
|
||||
)}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
labelClass(context) {
|
||||
const { hasErrors } = context;
|
||||
if (!hasErrors) {
|
||||
return `text-xs font-medium ${this.$dm(
|
||||
return `text-xs font-medium ${this.getThemeClass(
|
||||
'text-black-800',
|
||||
'dark:text-slate-50'
|
||||
)}`;
|
||||
}
|
||||
return `text-xs font-medium ${this.$dm(
|
||||
return `text-xs font-medium ${this.getThemeClass(
|
||||
'text-red-400',
|
||||
'dark:text-red-400'
|
||||
)}`;
|
||||
@@ -264,7 +266,7 @@ export default {
|
||||
v-if="shouldShowHeaderMessage"
|
||||
v-dompurify-html="formatMessage(headerMessage, false)"
|
||||
class="mb-4 text-sm leading-5 pre-chat-header-message"
|
||||
:class="$dm('text-black-800', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-800', 'dark:text-slate-50')"
|
||||
/>
|
||||
<FormulateInput
|
||||
v-for="item in enabledPreChatFields"
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||
ON_UNREAD_MESSAGE_CLICK,
|
||||
} from '../constants/widgetBusEvents';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
export default {
|
||||
name: 'UnreadMessage',
|
||||
components: { Thumbnail },
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
@@ -31,9 +31,15 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
const { formatMessage, getPlainText, truncateMessage, highlightContent } =
|
||||
useMessageFormatter();
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return {
|
||||
formatMessage,
|
||||
getPlainText,
|
||||
truncateMessage,
|
||||
highlightContent,
|
||||
getThemeClass,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -91,7 +97,7 @@ export default {
|
||||
<div class="chat-bubble-wrap">
|
||||
<button
|
||||
class="chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-50')"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-50')"
|
||||
@click="onClickMessage"
|
||||
>
|
||||
<div v-if="showSender" class="row--agent-block">
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script>
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
@@ -16,9 +15,8 @@ export default {
|
||||
},
|
||||
setup() {
|
||||
const { truncateMessage } = useMessageFormatter();
|
||||
return {
|
||||
truncateMessage,
|
||||
};
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass, truncateMessage };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -27,7 +25,7 @@ export default {
|
||||
<div
|
||||
v-if="!!items.length"
|
||||
class="chat-bubble agent"
|
||||
:class="$dm('bg-white', 'dark:bg-slate-700')"
|
||||
:class="getThemeClass('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">
|
||||
@@ -35,15 +33,16 @@ export default {
|
||||
<FluentIcon
|
||||
icon="link"
|
||||
class="mr-1"
|
||||
:class="$dm('text-black-900', 'dark:text-slate-50')"
|
||||
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
|
||||
/>
|
||||
<span :class="$dm('text-slate-900', 'dark:text-slate-50')">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<span
|
||||
:class="getThemeClass('text-slate-900', 'dark:text-slate-50')"
|
||||
>{{ item.title }}</span
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="description"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-200')"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-200')"
|
||||
>
|
||||
{{ truncateMessage(item.description) }}
|
||||
</span>
|
||||
|
||||
@@ -6,14 +6,13 @@ import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
messageId: {
|
||||
type: Number,
|
||||
@@ -25,7 +24,8 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { v$: useVuelidate(), getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -47,9 +47,9 @@ export default {
|
||||
);
|
||||
},
|
||||
inputColor() {
|
||||
return `${this.$dm('bg-white', 'dark:bg-slate-600')}
|
||||
${this.$dm('text-black-900', 'dark:text-slate-50')}
|
||||
${this.$dm('border-black-200', 'dark:border-black-500')}`;
|
||||
return `${this.getThemeClass('bg-white', 'dark:bg-slate-600')}
|
||||
${this.getThemeClass('text-black-900', 'dark:text-slate-50')}
|
||||
${this.getThemeClass('border-black-200', 'dark:border-black-500')}`;
|
||||
},
|
||||
inputHasError() {
|
||||
return this.v$.email.$error
|
||||
|
||||
71
app/javascript/widget/composables/specs/useDarkMode.spec.js
Normal file
71
app/javascript/widget/composables/specs/useDarkMode.spec.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { useDarkMode } from '../useDarkMode';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
vi.mock('dashboard/composables/store', () => ({
|
||||
useMapGetter: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('useDarkMode', () => {
|
||||
let mockDarkMode;
|
||||
|
||||
beforeEach(() => {
|
||||
mockDarkMode = { value: 'light' };
|
||||
vi.mocked(useMapGetter).mockReturnValue(mockDarkMode);
|
||||
});
|
||||
|
||||
it('returns darkMode, prefersDarkMode, and getThemeClass', () => {
|
||||
const result = useDarkMode();
|
||||
expect(result).toHaveProperty('darkMode');
|
||||
expect(result).toHaveProperty('prefersDarkMode');
|
||||
expect(result).toHaveProperty('getThemeClass');
|
||||
});
|
||||
|
||||
describe('prefersDarkMode', () => {
|
||||
it('returns false when darkMode is light', () => {
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
expect(prefersDarkMode.value).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when darkMode is dark', () => {
|
||||
mockDarkMode.value = 'dark';
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
expect(prefersDarkMode.value).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when darkMode is auto and OS prefers dark mode', () => {
|
||||
mockDarkMode.value = 'auto';
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: true });
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
expect(prefersDarkMode.value).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when darkMode is auto and OS prefers light mode', () => {
|
||||
mockDarkMode.value = 'auto';
|
||||
vi.spyOn(window, 'matchMedia').mockReturnValue({ matches: false });
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
expect(prefersDarkMode.value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getThemeClass', () => {
|
||||
it('returns light class when darkMode is light', () => {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
expect(getThemeClass('light-class', 'dark-class')).toBe('light-class');
|
||||
});
|
||||
|
||||
it('returns dark class when darkMode is dark', () => {
|
||||
mockDarkMode.value = 'dark';
|
||||
const { getThemeClass } = useDarkMode();
|
||||
expect(getThemeClass('light-class', 'dark-class')).toBe('dark-class');
|
||||
});
|
||||
|
||||
it('returns both classes when darkMode is auto', () => {
|
||||
mockDarkMode.value = 'auto';
|
||||
const { getThemeClass } = useDarkMode();
|
||||
expect(getThemeClass('light-class', 'dark-class')).toBe(
|
||||
'light-class dark-class'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
39
app/javascript/widget/composables/useDarkMode.js
Normal file
39
app/javascript/widget/composables/useDarkMode.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { computed } from 'vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
const isDarkModeAuto = mode => mode === 'auto';
|
||||
const isDarkMode = mode => mode === 'dark';
|
||||
|
||||
const getSystemPreference = () =>
|
||||
window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
|
||||
|
||||
const calculatePrefersDarkMode = (mode, systemPreference) =>
|
||||
isDarkModeAuto(mode) ? systemPreference : isDarkMode(mode);
|
||||
|
||||
const calculateThemeClass = (mode, light, dark) => {
|
||||
if (isDarkModeAuto(mode)) return `${light} ${dark}`;
|
||||
return isDarkMode(mode) ? dark : light;
|
||||
};
|
||||
|
||||
/**
|
||||
* Composable for handling dark mode.
|
||||
* @returns {Object} An object containing computed properties and methods for dark mode.
|
||||
*/
|
||||
export function useDarkMode() {
|
||||
const darkMode = useMapGetter('appConfig/darkMode');
|
||||
|
||||
const systemPreference = computed(getSystemPreference);
|
||||
|
||||
const prefersDarkMode = computed(() =>
|
||||
calculatePrefersDarkMode(darkMode.value, systemPreference.value)
|
||||
);
|
||||
|
||||
const getThemeClass = (light, dark) =>
|
||||
calculateThemeClass(darkMode.value, light, dark);
|
||||
|
||||
return {
|
||||
darkMode,
|
||||
prefersDarkMode,
|
||||
getThemeClass,
|
||||
};
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters({ darkMode: 'appConfig/darkMode' }),
|
||||
prefersDarkMode() {
|
||||
const isOSOnDarkMode =
|
||||
this.darkMode === 'auto' &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
return isOSOnDarkMode || this.darkMode === 'dark';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
$dm(light, dark) {
|
||||
if (this.darkMode === 'light') {
|
||||
return light;
|
||||
}
|
||||
if (this.darkMode === 'dark') {
|
||||
return dark;
|
||||
}
|
||||
return light + ' ' + dark;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import ArticleHero from 'widget/components/ArticleHero.vue';
|
||||
import ArticleCardSkeletonLoader from 'widget/components/ArticleCardSkeletonLoader.vue';
|
||||
|
||||
import { mapGetters } from 'vuex';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
|
||||
@@ -15,7 +15,11 @@ export default {
|
||||
TeamAvailability,
|
||||
ArticleCardSkeletonLoader,
|
||||
},
|
||||
mixins: [configMixin, routerMixin, darkModeMixin],
|
||||
mixins: [configMixin, routerMixin],
|
||||
setup() {
|
||||
const { prefersDarkMode } = useDarkMode();
|
||||
return { prefersDarkMode };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
availableAgents: 'agent/availableAgents',
|
||||
|
||||
Reference in New Issue
Block a user