feat: Add RTL Support to Widget (#11022)

This PR adds RTL support to the web widget for improved right-to-left language compatibility, updates colors, and cleans up code.

Fixes https://linear.app/chatwoot/issue/CW-4089/rtl-issues-on-widget

https://github.com/chatwoot/chatwoot/issues/9791

Other PR: https://github.com/chatwoot/chatwoot/pull/11016
This commit is contained in:
Sivin Varghese
2025-03-21 22:09:03 +05:30
committed by GitHub
parent e4ea078e52
commit 3a693947b5
76 changed files with 966 additions and 2406 deletions

View File

@@ -1,7 +1,6 @@
<script>
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import { useDarkMode } from 'widget/composables/useDarkMode';
export default {
components: {
@@ -15,8 +14,7 @@ export default {
},
setup() {
const { truncateMessage } = useMessageFormatter();
const { getThemeClass } = useDarkMode();
return { getThemeClass, truncateMessage };
return { truncateMessage };
},
};
</script>
@@ -25,52 +23,29 @@ export default {
<template>
<div
v-if="!!items.length"
class="chat-bubble agent"
:class="getThemeClass('bg-white', 'dark:bg-slate-700')"
class="chat-bubble agent bg-n-background dark:bg-n-solid-3"
>
<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">
<FluentIcon
icon="link"
class="mr-1"
:class="getThemeClass('text-black-900', 'dark:text-slate-50')"
/>
<span :class="getThemeClass('text-slate-900', 'dark:text-slate-50')">
<div
v-for="item in items"
:key="item.link"
class="border-b border-solid border-n-weak text-sm py-2 px-0 last:border-b-0"
>
<a
:href="item.link"
target="_blank"
rel="noopener noreferrer nofollow"
class="text-n-slate-12 no-underline"
>
<span class="flex items-center text-black-900 font-medium">
<FluentIcon icon="link" class="ltr:mr-1 rtl:ml-1 text-n-slate-12" />
<span class="text-n-slate-12">
{{ item.title }}
</span>
</span>
<span
class="description"
:class="getThemeClass('text-slate-700', 'dark:text-slate-200')"
>
<span class="block mt-1 text-n-slate-12">
{{ truncateMessage(item.description) }}
</span>
</a>
</div>
</div>
</template>
<style lang="scss" scoped>
@import 'widget/assets/scss/variables.scss';
.article-item {
border-bottom: 1px solid $color-border;
font-size: $font-size-default;
padding: $space-small 0;
a {
color: $color-body;
text-decoration: none;
}
.description {
display: block;
margin-top: $space-smaller;
}
&:last-child {
border-bottom: 0;
}
}
</style>

View File

@@ -6,7 +6,6 @@ import { getContrastingTextColor } from '@chatwoot/utils';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import Spinner from 'shared/components/Spinner.vue';
import { useDarkMode } from 'widget/composables/useDarkMode';
export default {
components: {
@@ -24,8 +23,7 @@ export default {
},
},
setup() {
const { getThemeClass } = useDarkMode();
return { v$: useVuelidate(), getThemeClass };
return { v$: useVuelidate() };
},
data() {
return {
@@ -46,16 +44,6 @@ export default {
this.messageContentAttributes.submitted_email
);
},
inputColor() {
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
? `${this.inputColor} error`
: `${this.inputColor}`;
},
},
validations: {
email: {
@@ -88,14 +76,14 @@ export default {
<div>
<form
v-if="!hasSubmitted"
class="email-input-group"
class="email-input-group h-10 flex my-2 mx-0 min-w-[200px]"
@submit.prevent="onSubmit"
>
<input
v-model="email"
class="form-input"
type="email"
:placeholder="$t('EMAIL_PLACEHOLDER')"
:class="inputHasError"
:class="{ error: v$.email.$error }"
@input="v$.email.$touch"
@keydown.enter="onSubmit"
/>
@@ -116,34 +104,21 @@ export default {
</template>
<style lang="scss" scoped>
@import 'widget/assets/scss/variables.scss';
.email-input-group {
display: flex;
margin: $space-small 0;
min-width: 200px;
input {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
padding: $space-one;
width: 100%;
@apply dark:bg-n-alpha-black1 rtl:rounded-tl-[0] ltr:rounded-tr-[0] rtl:rounded-bl-[0] ltr:rounded-br-[0] p-2.5 w-full focus:ring-0 focus:outline-n-brand;
&::placeholder {
color: $color-light-gray;
@apply text-n-slate-10;
}
&.error {
border-color: $color-error;
@apply outline-n-ruby-8 dark:outline-n-ruby-8 hover:outline-n-ruby-9 dark:hover:outline-n-ruby-9;
}
}
.button {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
font-size: $font-size-large;
height: auto;
margin-left: -1px;
@apply rtl:rounded-tr-[0] ltr:rounded-tl-[0] rtl:rounded-br-[0] ltr:rounded-bl-[0] rounded-lg h-auto ltr:-ml-px rtl:-mr-px text-xl;
.spinner {
display: block;

View File

@@ -62,7 +62,7 @@ export default {
}"
@click="joinTheCall"
>
<FluentIcon icon="video-add" class="mr-2" />
<FluentIcon icon="video-add" class="rtl:ml-2 ltr:mr-2" />
{{ $t('INTEGRATIONS.DYTE.CLICK_HERE_TO_JOIN') }}
</button>
<div v-if="dyteAuthToken" class="video-call--container">
@@ -81,8 +81,6 @@ export default {
</template>
<style lang="scss" scoped>
@import 'widget/assets/scss/variables.scss';
.video-call--container {
position: fixed;
top: 72px;
@@ -101,15 +99,10 @@ export default {
}
.join-call-button {
margin: $space-small 0;
border-radius: 4px;
display: flex;
align-items: center;
@apply flex items-center my-2 rounded-lg;
}
.leave-room-button {
position: absolute;
top: 0;
right: $space-small;
@apply absolute top-0 ltr:right-2 rtl:left-2 px-1 rounded-md;
}
</style>