Revert "chore: update conversation sidebar interactions (#13988)"
This reverts commit 787fcc4a95.
This commit is contained in:
@@ -107,10 +107,11 @@ const closeMobileSidebar = () => {
|
||||
size="sm"
|
||||
/>
|
||||
<ComposeConversation :contact-id="contactId">
|
||||
<template #trigger>
|
||||
<template #trigger="{ toggle }">
|
||||
<Button
|
||||
:label="$t('CONTACTS_LAYOUT.HEADER.SEND_MESSAGE')"
|
||||
size="sm"
|
||||
@click="toggle"
|
||||
/>
|
||||
</template>
|
||||
</ComposeConversation>
|
||||
|
||||
@@ -114,8 +114,8 @@ const emit = defineEmits([
|
||||
</div>
|
||||
<div class="w-px h-4 bg-n-strong" />
|
||||
<ComposeConversation>
|
||||
<template #trigger>
|
||||
<Button :label="buttonLabel" size="sm" />
|
||||
<template #trigger="{ toggle }">
|
||||
<Button :label="buttonLabel" size="sm" @click="toggle" />
|
||||
</template>
|
||||
</ComposeConversation>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
import { reactive, ref, computed, onMounted, watch } from 'vue';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import {
|
||||
@@ -15,18 +18,22 @@ import {
|
||||
processContactableInboxes,
|
||||
mergeInboxDetails,
|
||||
} from 'dashboard/components-next/NewConversation/helpers/composeConversationHelper';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
|
||||
import Popover from 'dashboard/components-next/popover/Popover.vue';
|
||||
import ComposeNewConversationForm from 'dashboard/components-next/NewConversation/components/ComposeNewConversationForm.vue';
|
||||
|
||||
const props = defineProps({
|
||||
alignPosition: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
},
|
||||
contactId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: 'end',
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -35,16 +42,23 @@ const emit = defineEmits(['close']);
|
||||
const searchContacts = createContactSearcher();
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
const { width: windowWidth } = useWindowSize();
|
||||
|
||||
const { fetchSignatureFlagFromUISettings } = useUISettings();
|
||||
|
||||
const popoverRef = ref(null);
|
||||
const isSmallScreen = computed(
|
||||
() => windowWidth.value < wootConstants.SMALL_SCREEN_BREAKPOINT
|
||||
);
|
||||
|
||||
const viewInModal = computed(() => props.isModal || isSmallScreen.value);
|
||||
|
||||
const contacts = ref([]);
|
||||
const selectedContact = ref(null);
|
||||
const targetInbox = ref(null);
|
||||
const isCreatingContact = ref(false);
|
||||
const isFetchingInboxes = ref(false);
|
||||
const isSearching = ref(false);
|
||||
const showComposeNewConversation = ref(false);
|
||||
|
||||
const formState = reactive({
|
||||
message: '',
|
||||
@@ -81,6 +95,14 @@ const directUploadsEnabled = computed(
|
||||
|
||||
const activeContact = computed(() => contactById.value(props.contactId));
|
||||
|
||||
const composePopoverClass = computed(() => {
|
||||
if (viewInModal.value) return '';
|
||||
|
||||
return props.alignPosition === 'right'
|
||||
? 'absolute ltr:left-0 ltr:right-[unset] rtl:right-0 rtl:left-[unset]'
|
||||
: 'absolute rtl:left-0 rtl:right-[unset] ltr:right-0 ltr:left-[unset]';
|
||||
});
|
||||
|
||||
const onContactSearch = debounce(
|
||||
async query => {
|
||||
isSearching.value = true;
|
||||
@@ -150,7 +172,7 @@ const clearSelectedContact = () => {
|
||||
};
|
||||
|
||||
const closeCompose = () => {
|
||||
popoverRef.value?.hide();
|
||||
showComposeNewConversation.value = false;
|
||||
if (!props.contactId) {
|
||||
// If contactId is passed as prop
|
||||
// Then don't allow to remove the selected contact
|
||||
@@ -158,6 +180,7 @@ const closeCompose = () => {
|
||||
}
|
||||
targetInbox.value = null;
|
||||
resetContacts();
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const discardCompose = () => {
|
||||
@@ -190,15 +213,8 @@ const createConversation = async ({ payload, isFromWhatsApp }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onPopoverShow = () => {
|
||||
// Flag to prevent triggering drag n drop,
|
||||
// When compose modal is active
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, true);
|
||||
};
|
||||
|
||||
const onPopoverHide = () => {
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, false);
|
||||
emit('close');
|
||||
const toggle = () => {
|
||||
showComposeNewConversation.value = !showComposeNewConversation.value;
|
||||
};
|
||||
|
||||
watch(
|
||||
@@ -226,22 +242,64 @@ watch(
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
const handleClickOutside = () => {
|
||||
if (!showComposeNewConversation.value) return;
|
||||
|
||||
showComposeNewConversation.value = false;
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const onModalBackdropClick = () => {
|
||||
if (!viewInModal.value) return;
|
||||
handleClickOutside();
|
||||
};
|
||||
|
||||
onMounted(() => resetContacts());
|
||||
|
||||
const keyboardEvents = {
|
||||
Escape: {
|
||||
action: () => {
|
||||
if (showComposeNewConversation.value) {
|
||||
showComposeNewConversation.value = false;
|
||||
emit('close');
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, false);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
useKeyboardEvents(keyboardEvents);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Popover
|
||||
ref="popoverRef"
|
||||
:align="align"
|
||||
@show="onPopoverShow"
|
||||
@hide="onPopoverHide"
|
||||
<div
|
||||
v-on-click-outside="[
|
||||
handleClickOutside,
|
||||
// Fixed and edge case https://github.com/chatwoot/chatwoot/issues/10785
|
||||
// This will prevent closing the compose conversation modal when the editor Create link popup is open
|
||||
{ ignore: ['dialog.ProseMirror-prompt-backdrop'] },
|
||||
]"
|
||||
class="relative"
|
||||
:class="{
|
||||
'z-50': showComposeNewConversation && !viewInModal,
|
||||
}"
|
||||
>
|
||||
<template #default="{ isOpen }">
|
||||
<slot name="trigger" :is-open="isOpen" />
|
||||
</template>
|
||||
<template #content>
|
||||
<slot
|
||||
name="trigger"
|
||||
:is-open="showComposeNewConversation"
|
||||
:toggle="toggle"
|
||||
/>
|
||||
<div
|
||||
v-if="showComposeNewConversation"
|
||||
:class="{
|
||||
'fixed z-50 bg-n-alpha-black1 backdrop-blur-[4px] flex items-start pt-[clamp(3rem,15vh,12rem)] justify-center inset-0':
|
||||
viewInModal,
|
||||
}"
|
||||
@click.self="onModalBackdropClick"
|
||||
>
|
||||
<ComposeNewConversationForm
|
||||
:form-state="formState"
|
||||
:class="[{ 'mt-2': !viewInModal }, composePopoverClass]"
|
||||
:contacts="contacts"
|
||||
:contact-id="contactId"
|
||||
:is-loading="isSearching"
|
||||
@@ -263,6 +321,6 @@ onMounted(() => resetContacts());
|
||||
@create-conversation="createConversation"
|
||||
@discard="discardCompose"
|
||||
/>
|
||||
</template>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+1
-1
@@ -361,7 +361,7 @@ useKeyboardEvents({
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="w-full md:w-[42rem] divide-y divide-n-strong overflow-visible transition-all duration-300 ease-in-out top-full flex flex-col bg-n-alpha-3 border border-n-strong shadow-sm backdrop-blur-[100px] rounded-xl min-w-0 max-h-[calc(100vh-8rem)]"
|
||||
class="w-[42rem] divide-y divide-n-strong overflow-visible transition-all duration-300 ease-in-out top-full flex flex-col bg-n-alpha-3 border border-n-strong shadow-sm backdrop-blur-[100px] rounded-xl min-w-0 max-h-[calc(100vh-8rem)]"
|
||||
>
|
||||
<div class="flex-1 overflow-y-auto divide-y divide-n-strong">
|
||||
<ContactSelector
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch, nextTick } from 'vue';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { useBreakpoints, breakpointsTailwind } from '@vueuse/core';
|
||||
import { useDropdownPosition } from 'dashboard/composables/useDropdownPosition';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import TeleportWithDirection from 'dashboard/components-next/TeleportWithDirection.vue';
|
||||
|
||||
const props = defineProps({
|
||||
align: {
|
||||
type: String,
|
||||
default: 'end',
|
||||
validator: v => ['start', 'end'].includes(v),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['show', 'hide']);
|
||||
|
||||
const isActive = ref(false);
|
||||
const triggerRef = ref(null);
|
||||
const popoverRef = ref(null);
|
||||
const mobileContentRef = ref(null);
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||
const isMobile = breakpoints.smaller('md');
|
||||
const showPopover = computed(() => isActive.value && !isMobile.value);
|
||||
|
||||
const { fixedPosition, updatePosition } = useDropdownPosition(
|
||||
triggerRef,
|
||||
popoverRef,
|
||||
showPopover,
|
||||
{ align: props.align }
|
||||
);
|
||||
|
||||
const show = async () => {
|
||||
isActive.value = true;
|
||||
if (!isMobile.value) {
|
||||
await nextTick();
|
||||
updatePosition();
|
||||
}
|
||||
emit('show');
|
||||
};
|
||||
|
||||
const hide = () => {
|
||||
if (!isActive.value) return;
|
||||
isActive.value = false;
|
||||
emit('hide');
|
||||
};
|
||||
|
||||
const toggle = async () => {
|
||||
if (isActive.value) hide();
|
||||
else await show();
|
||||
};
|
||||
|
||||
// Recalculate position when switching from mobile to desktop while open
|
||||
watch(isMobile, async mobile => {
|
||||
if (!isActive.value || mobile) return;
|
||||
await nextTick();
|
||||
updatePosition();
|
||||
});
|
||||
|
||||
const handleClickOutside = event => {
|
||||
if (triggerRef.value?.contains(event.target)) return;
|
||||
hide();
|
||||
};
|
||||
|
||||
// Selectors for teleported elements that should not trigger close
|
||||
const clickOutsideIgnore = [
|
||||
'dialog.ProseMirror-prompt-backdrop',
|
||||
'[data-popover-content]',
|
||||
];
|
||||
|
||||
useKeyboardEvents({
|
||||
Escape: {
|
||||
action: () => isActive.value && hide(),
|
||||
allowOnFocusedInput: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineExpose({ show, hide, toggle });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span ref="triggerRef" class="inline-flex" @click="toggle">
|
||||
<slot :is-open="isActive" />
|
||||
</span>
|
||||
|
||||
<TeleportWithDirection to="body">
|
||||
<!-- Mobile: centered modal with backdrop -->
|
||||
<div
|
||||
v-if="isActive && isMobile"
|
||||
data-popover-backdrop
|
||||
class="fixed inset-0 z-[9999] flex items-start pt-[clamp(3rem,15vh,12rem)] justify-center bg-n-alpha-black1"
|
||||
>
|
||||
<div
|
||||
ref="mobileContentRef"
|
||||
v-on-click-outside="[
|
||||
handleClickOutside,
|
||||
{ ignore: clickOutsideIgnore },
|
||||
]"
|
||||
data-popover-content
|
||||
class="relative w-full max-w-lg max-h-[calc(100vh-4rem)] mx-4 overflow-y-auto bg-n-alpha-3 backdrop-blur-[100px] shadow-xl rounded-xl"
|
||||
>
|
||||
<slot name="content" :hide="hide" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop: fixed popover -->
|
||||
<div
|
||||
v-else-if="showPopover"
|
||||
ref="popoverRef"
|
||||
v-on-click-outside="[handleClickOutside, { ignore: clickOutsideIgnore }]"
|
||||
data-popover-content
|
||||
:class="fixedPosition.class"
|
||||
:style="fixedPosition.style"
|
||||
class="bg-n-alpha-3 backdrop-blur-[100px] shadow-xl rounded-xl overflow-y-auto max-h-[calc(100vh-2rem)]"
|
||||
>
|
||||
<slot name="content" :hide="hide" />
|
||||
</div>
|
||||
</TeleportWithDirection>
|
||||
</template>
|
||||
@@ -10,6 +10,8 @@ import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useWindowSize, useEventListener } from '@vueuse/core';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import SidebarGroup from './SidebarGroup.vue';
|
||||
@@ -182,6 +184,15 @@ const closeMobileSidebar = () => {
|
||||
emit('closeMobileSidebar');
|
||||
};
|
||||
|
||||
const onComposeOpen = toggleFn => {
|
||||
toggleFn();
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, true);
|
||||
};
|
||||
|
||||
const onComposeClose = () => {
|
||||
emitter.emit(BUS_EVENTS.NEW_CONVERSATION_MODAL, false);
|
||||
};
|
||||
|
||||
const newReportRoutes = () => [
|
||||
{
|
||||
name: 'Reports Agent',
|
||||
@@ -723,13 +734,7 @@ const menuItems = computed(() => {
|
||||
<aside
|
||||
v-on-click-outside="[
|
||||
closeMobileSidebar,
|
||||
{
|
||||
ignore: [
|
||||
'#mobile-sidebar-launcher',
|
||||
'[data-popover-content]',
|
||||
'[data-popover-backdrop]',
|
||||
],
|
||||
},
|
||||
{ ignore: ['#mobile-sidebar-launcher'] },
|
||||
]"
|
||||
class="bg-n-background flex flex-col text-sm pb-px fixed top-0 ltr:left-0 rtl:right-0 h-full z-40 w-[200px] md:w-auto md:relative md:flex-shrink-0 md:ltr:translate-x-0 md:rtl:translate-x-0 ltr:border-r rtl:border-l border-n-weak"
|
||||
:class="[
|
||||
@@ -797,8 +802,8 @@ const menuItems = computed(() => {
|
||||
>
|
||||
<span class="i-lucide-search size-4 text-n-slate-11" />
|
||||
</RouterLink>
|
||||
<ComposeConversation align="start">
|
||||
<template #trigger="{ isOpen }">
|
||||
<ComposeConversation align-position="right" @close="onComposeClose">
|
||||
<template #trigger="{ toggle, isOpen }">
|
||||
<Button
|
||||
icon="i-lucide-pen-line"
|
||||
color="slate"
|
||||
@@ -810,6 +815,7 @@ const menuItems = computed(() => {
|
||||
: '!h-7 !outline-n-weak !text-n-slate-11',
|
||||
{ '!bg-n-alpha-2 dark:!bg-n-slate-9/30': isOpen },
|
||||
]"
|
||||
@click="onComposeOpen(toggle)"
|
||||
/>
|
||||
</template>
|
||||
</ComposeConversation>
|
||||
|
||||
Reference in New Issue
Block a user