fix: Fix bubble design in RTL (#10683)

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Shivam Mishra
2025-01-15 00:44:13 +05:30
committed by GitHub
parent b3d0d466ee
commit 7b31b5ad6e
8 changed files with 76 additions and 19 deletions

View File

@@ -1,9 +1,11 @@
<script setup>
import { computed, ref, toRefs } from 'vue';
import { onMounted, computed, ref, toRefs } from 'vue';
import { useTimeoutFn } from '@vueuse/core';
import { provideMessageContext } from './provider.js';
import { useTrack } from 'dashboard/composables';
import { emitter } from 'shared/helpers/mitt';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { LocalStorage } from 'shared/helpers/localStorage';
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
@@ -115,7 +117,7 @@ const props = defineProps({
createdAt: { type: Number, required: true }, // eslint-disable-line vue/no-unused-properties
currentUserId: { type: Number, required: true },
groupWithNext: { type: Boolean, default: false },
inboxId: { type: Number, required: true }, // eslint-disable-line vue/no-unused-properties
inboxId: { type: Number, default: null }, // eslint-disable-line vue/no-unused-properties
inboxSupportsReplyTo: { type: Object, default: () => ({}) },
inReplyTo: { type: Object, default: null }, // eslint-disable-line vue/no-unused-properties
isEmailInbox: { type: Boolean, default: false },
@@ -127,8 +129,10 @@ const props = defineProps({
});
const contextMenuPosition = ref({});
const showBackgroundHighlight = ref(false);
const showContextMenu = ref(false);
const { t } = useI18n();
const route = useRoute();
/**
* Computes the message variant based on props
@@ -329,6 +333,22 @@ const contextMenuEnabledOptions = computed(() => {
};
});
const shouldRenderMessage = computed(() => {
const hasAttachments = !!(props.attachments && props.attachments.length > 0);
const isEmailContentType = props.contentType === CONTENT_TYPES.INCOMING_EMAIL;
const isUnsupported = props.contentAttributes?.isUnsupported;
const isAnIntegrationMessage =
props.contentType === CONTENT_TYPES.INTEGRATIONS;
return (
hasAttachments ||
props.content ||
isEmailContentType ||
isUnsupported ||
isAnIntegrationMessage
);
});
function openContextMenu(e) {
const shouldSkipContextMenu =
e.target?.classList.contains('skip-context-menu') ||
@@ -382,6 +402,20 @@ const avatarInfo = computed(() => {
};
});
const setupHighlightTimer = () => {
if (Number(route.query.messageId) !== Number(props.id)) {
return;
}
showBackgroundHighlight.value = true;
const HIGHLIGHT_TIMER = 1000;
useTimeoutFn(() => {
showBackgroundHighlight.value = false;
}, HIGHLIGHT_TIMER);
};
onMounted(setupHighlightTimer);
provideMessageContext({
...toRefs(props),
isPrivate: computed(() => props.private),
@@ -392,14 +426,19 @@ provideMessageContext({
});
</script>
<!-- eslint-disable-next-line vue/no-root-v-if -->
<template>
<div
v-if="shouldRenderMessage"
:id="`message${props.id}`"
class="flex w-full message-bubble-container"
:data-message-id="props.id"
:class="[
flexOrientationClass,
shouldGroupWithNext ? 'group-with-next mb-2' : 'mb-4',
{
'bg-n-alpha-1': showBackgroundHighlight,
},
]"
>
<div v-if="variant === MESSAGE_VARIANTS.ACTIVITY">
@@ -428,7 +467,7 @@ provideMessageContext({
<div
class="[grid-area:bubble] flex"
:class="{
'pl-9 justify-end': orientation === ORIENTATION.RIGHT,
'ltr:pl-9 rtl:pl-0 justify-end': orientation === ORIENTATION.RIGHT,
'min-w-0': variant === MESSAGE_VARIANTS.EMAIL,
}"
@contextmenu="openContextMenu($event)"
@@ -461,11 +500,11 @@ provideMessageContext({
<style lang="scss">
.group-with-next + .message-bubble-container {
.left-bubble {
@apply rounded-tl-sm;
@apply ltr:rounded-tl-sm rtl:rounded-tr-sm;
}
.right-bubble {
@apply rounded-tr-sm;
@apply ltr:rounded-tr-sm rtl:rounded-tl-sm;
}
}
</style>

View File

@@ -28,8 +28,8 @@ const { t } = useI18n();
<div
class="absolute bg-n-alpha-3 px-4 py-3 border rounded-xl border-n-strong text-n-slate-12 bottom-6 w-52 text-xs backdrop-blur-[100px] shadow-[0px_0px_24px_0px_rgba(0,0,0,0.12)] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all"
:class="{
'left-0': orientation === ORIENTATION.LEFT,
'right-0': orientation === ORIENTATION.RIGHT,
'ltr:left-0 rtl:right-0': orientation === ORIENTATION.LEFT,
'ltr:right-0 rtl:left-0': orientation === ORIENTATION.RIGHT,
}"
>
{{ error }}

View File

@@ -13,13 +13,10 @@ const readableTime = computed(() =>
<template>
<BaseBubble
class="px-2 py-0.5 !rounded-full flex items-center gap-2"
v-tooltip.top="readableTime"
class="px-2 py-0.5 !rounded-full flex min-w-0 items-center gap-2"
data-bubble-name="activity"
>
<span v-dompurify-html="content" :title="content" class="truncate" />
<div v-if="readableTime" class="w-px h-3 rounded-full bg-n-slate-7" />
<time class="text-n-slate-10 truncate flex-shrink" :title="readableTime">
{{ readableTime }}
</time>
<span v-dompurify-html="content" :title="content" />
</BaseBubble>
</template>

View File

@@ -29,8 +29,10 @@ const varaintBaseMap = {
};
const orientationMap = {
[ORIENTATION.LEFT]: 'left-bubble rounded-xl rounded-bl-sm',
[ORIENTATION.RIGHT]: 'right-bubble rounded-xl rounded-br-sm',
[ORIENTATION.LEFT]:
'left-bubble rounded-xl ltr:rounded-bl-sm rtl:rounded-br-sm',
[ORIENTATION.RIGHT]:
'right-bubble rounded-xl ltr:rounded-br-sm rtl:rounded-bl-sm',
[ORIENTATION.CENTER]: 'rounded-md',
};

View File

@@ -55,7 +55,7 @@ const showMeta = computed(() => {
<template>
<section
v-show="showMeta"
class="space-y-1 pr-9 border-b border-n-strong text-sm"
class="space-y-1 rtl:pl-9 ltr:pr-9 border-b border-n-strong text-sm break-words"
:class="hasError ? 'text-n-ruby-11' : 'text-n-slate-11'"
>
<template v-if="showMeta">

View File

@@ -109,7 +109,10 @@ const textToShow = computed(() => {
</button>
</div>
</section>
<section v-if="attachments.length" class="px-4 pb-4 space-y-2">
<section
v-if="Array.isArray(attachments) && attachments.length"
class="px-4 pb-4 space-y-2"
>
<AttachmentChips :attachments="attachments" class="gap-1" />
</section>
</BaseBubble>

View File

@@ -14,7 +14,7 @@ const isTemplate = computed(() => {
});
const isEmpty = computed(() => {
return !content.value && !attachments.value.length;
return !content.value && !attachments.value?.length;
});
</script>

View File

@@ -109,15 +109,31 @@ const tailwindConfig = {
'ul li': {
margin: '0 0 0.5em 1em',
listStyleType: 'disc',
'[dir="rtl"] &': {
margin: '0 1em 0.5em 0',
},
},
'ol li': {
margin: '0 0 0.5em 1em',
listStyleType: 'decimal',
'[dir="rtl"] &': {
margin: '0 1em 0.5em 0',
},
},
blockquote: {
color: 'rgb(var(--slate-11))',
borderLeft: `4px solid rgb(var(--black-alpha-1))`,
paddingLeft: '1em',
'[dir="rtl"] &': {
borderLeft: 'none',
paddingLeft: '0',
borderRight: `4px solid rgb(var(--black-alpha-1))`,
paddingRight: '1em',
},
'[dir="ltr"] &': {
borderRight: 'none',
paddingRight: '0',
},
},
code: {
backgroundColor: 'rgb(var(--alpha-3))',
@@ -146,7 +162,7 @@ const tailwindConfig = {
padding: '0.75em',
color: 'rgb(var(--slate-12))',
border: `none`,
textAlign: 'left',
textAlign: 'start',
fontWeight: '600',
},
tr: {