chore: Update reply editor design (#10575)

# Pull Request Template

## Description

This PR will update the reply message editor’s design.

**Screen recording**


https://github.com/user-attachments/assets/40f61903-6bf7-4031-9a36-9027dffc46aa

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Sivin Varghese
2025-01-08 17:49:53 +05:30
committed by GitHub
parent fa8f1d9b6f
commit dd595675bc
14 changed files with 216 additions and 164 deletions

View File

@@ -10,9 +10,11 @@ import AIAssistanceModal from './AIAssistanceModal.vue';
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue';
import { emitter } from 'shared/helpers/mitt';
import NextButton from 'dashboard/components-next/button/Button.vue';
export default {
components: {
NextButton,
AIAssistanceModal,
AICTAModal,
AIAssistanceCTAButton,
@@ -128,13 +130,13 @@ export default {
v-if="shouldShowAIAssistCTAButton"
@open="openAIAssist"
/>
<woot-button
<NextButton
v-else
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
icon="wand"
color-scheme="secondary"
variant="smooth"
size="small"
icon="i-ph-magic-wand"
slate
faded
sm
@click="openAIAssist"
/>
<woot-modal

View File

@@ -1,27 +1,22 @@
<script>
export default {
emits: ['open'],
<script setup>
import NextButton from 'dashboard/components-next/button/Button.vue';
methods: {
onClick() {
this.$emit('open');
},
},
const emit = defineEmits(['open']);
const onClick = () => {
emit('open');
};
</script>
<template>
<div class="relative">
<woot-button
icon="wand"
color-scheme="secondary"
variant="smooth"
size="small"
class-names="cta-btn cta-btn-light dark:cta-btn-dark hover:cta-btn-light-hover dark:hover:cta-btn-dark-hover"
<NextButton
class="cta-btn cta-btn-light dark:cta-btn-dark hover:cta-btn-light-hover dark:hover:cta-btn-dark-hover"
:label="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
icon="i-ph-magic-wand"
sm
@click="onClick"
>
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST') }}
</woot-button>
/>
<div
class="radar-ping-animation absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500"
@@ -34,22 +29,26 @@ export default {
<style scoped>
@tailwind components;
@layer components {
/* Gradient animation */
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.cta-btn {
animation: gradient 5s ease infinite;
border: 0;
@apply text-n-slate-12 border-0 text-xs;
}
.cta-btn-light {
@@ -96,6 +95,7 @@ export default {
opacity: 0;
}
}
.radar-ping-animation {
animation: ping 1s ease infinite;
}

View File

@@ -2,8 +2,12 @@
import { mapGetters } from 'vuex';
import DyteAPI from 'dashboard/api/integrations/dyte';
import { useAlert } from 'dashboard/composables';
import NextButton from 'dashboard/components-next/button/Button.vue';
export default {
components: {
NextButton,
},
props: {
conversationId: {
type: Number,
@@ -43,16 +47,15 @@ export default {
<!-- eslint-disable-next-line vue/no-root-v-if -->
<template>
<woot-button
<NextButton
v-if="isVideoIntegrationEnabled"
v-tooltip.top-end="
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
"
icon="video"
:is-loading="isLoading"
color-scheme="secondary"
variant="smooth"
size="small"
icon="i-ph-video-camera"
slate
faded
sm
@click="onClick"
/>
</template>

View File

@@ -735,7 +735,7 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
.ProseMirror-menubar {
min-height: var(--space-two) !important;
@apply -ml-2.5 pb-0 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100;
@apply -ml-2.5 pb-0 bg-transparent text-n-slate-11;
.ProseMirror-menu-active {
@apply bg-slate-75 dark:bg-slate-800;
@@ -787,10 +787,6 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
}
.ProseMirror-menubar-wrapper {
.ProseMirror-menubar {
@apply bg-yellow-100 dark:bg-yellow-800 text-slate-700 dark:text-slate-25;
}
> .ProseMirror {
@apply text-slate-800 dark:text-slate-25;

View File

@@ -0,0 +1,64 @@
<script setup>
import { computed } from 'vue';
import { REPLY_EDITOR_MODES } from './constants';
import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({
mode: {
type: String,
default: REPLY_EDITOR_MODES.REPLY,
},
});
defineEmits(['toggleMode']);
const isPrivate = computed(() => props.mode === REPLY_EDITOR_MODES.NOTE);
</script>
<template>
<button
class="flex items-center h-8 p-1 transition-all border rounded-full duration-600 bg-n-alpha-2 dark:bg-n-alpha-2 hover:bg-n-alpha-1 dark:hover:brightness-105 group relative transition-all duration-300 ease-in-out"
:class="[
isPrivate
? 'border-n-amber-12/10 dark:border-n-amber-3/30 w-[128px]'
: 'border-n-weak dark:border-n-weak ',
]"
@click="$emit('toggleMode')"
>
<div
class="flex absolute items-center justify-center w-6 transition-all duration-200 rounded-full bg-n-alpha-black1 size-6 transition-all duration-300 ease-in-out"
:class="[
isPrivate
? 'ltr:translate-x-[94px] rtl:-translate-x-[94px]'
: 'translate-x-0',
]"
>
<Icon
:icon="
isPrivate
? 'i-material-symbols-lock'
: 'i-material-symbols-lock-open-rounded'
"
class="flex-shrink-0 size-3.5"
:class="[
isPrivate ? 'dark:text-n-amber-9 text-n-amber-11' : 'text-n-slate-10',
]"
/>
</div>
<span
class="flex items-center text-sm font-medium transition-all duration-200 w-fit whitespace-nowrap"
:class="[
isPrivate
? 'text-n-amber-12 ltr:mr-7 ltr:ml-1.5 rtl:ml-7 rtl:mr-1.5'
: 'text-n-slate-12 ltr:ml-7 ltr:mr-1.5 rtl:mr-7 rtl:ml-1.5',
]"
>
{{
isPrivate
? $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE')
: $t('CONVERSATION.REPLYBOX.REPLY')
}}
</span>
</button>
</template>

View File

@@ -15,10 +15,11 @@ import VideoCallButton from '../VideoCallButton.vue';
import AIAssistanceButton from '../AIAssistanceButton.vue';
import { REPLY_EDITOR_MODES } from './constants';
import { mapGetters } from 'vuex';
import NextButton from 'dashboard/components-next/button/Button.vue';
export default {
name: 'ReplyBottomPanel',
components: { FileUpload, VideoCallButton, AIAssistanceButton },
components: { NextButton, FileUpload, VideoCallButton, AIAssistanceButton },
mixins: [inboxMixin],
props: {
mode: {
@@ -207,13 +208,13 @@ export default {
switch (this.recordingAudioState) {
// playing paused recording stopped inactive destroyed
case 'playing':
return 'microphone-pause';
return 'i-ph-pause';
case 'paused':
return 'microphone-play';
return 'i-ph-play';
case 'stopped':
return 'microphone-play';
return 'i-ph-play';
default:
return 'microphone-stop';
return 'i-ph-stop';
}
},
showMessageSignatureButton() {
@@ -253,15 +254,14 @@ export default {
</script>
<template>
<div class="bottom-box" :class="wrapClass">
<div class="flex justify-between p-3" :class="wrapClass">
<div class="left-wrap">
<woot-button
<NextButton
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
icon="emoji"
color-scheme="secondary"
variant="smooth"
size="small"
icon="i-ph-smiley-sticker"
slate
faded
sm
@click="toggleEmojiPicker"
/>
<FileUpload
@@ -279,62 +279,59 @@ export default {
}"
@input-file="onFileUpload"
>
<woot-button
<NextButton
v-if="showAttachButton"
class-names="button--upload"
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
icon="attach"
color-scheme="secondary"
variant="smooth"
size="small"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
icon="i-ph-paperclip"
slate
faded
sm
/>
</FileUpload>
<woot-button
<NextButton
v-if="showAudioRecorderButton"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_ICON')"
:icon="!isRecordingAudio ? 'microphone' : 'microphone-off'"
:color-scheme="!isRecordingAudio ? 'secondary' : 'alert'"
variant="smooth"
size="small"
:icon="!isRecordingAudio ? 'i-ph-microphone' : 'i-ph-microphone-slash'"
slate
faded
sm
@click="toggleAudioRecorder"
/>
<woot-button
<NextButton
v-if="showEditorToggle"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
icon="quote"
color-scheme="secondary"
variant="smooth"
size="small"
icon="i-ph-quotes"
slate
faded
sm
@click="$emit('toggleEditor')"
/>
<woot-button
<NextButton
v-if="showAudioPlayStopButton"
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
:icon="audioRecorderPlayStopIcon"
color-scheme="secondary"
variant="smooth"
size="small"
slate
faded
sm
:label="recordingAudioDurationText"
@click="toggleAudioRecorderPlayPause"
>
<span>{{ recordingAudioDurationText }}</span>
</woot-button>
<woot-button
/>
<NextButton
v-if="showMessageSignatureButton"
v-tooltip.top-end="signatureToggleTooltip"
icon="signature"
color-scheme="secondary"
variant="smooth"
size="small"
:title="signatureToggleTooltip"
icon="i-ph-signature"
slate
faded
sm
@click="toggleMessageSignature"
/>
<woot-button
<NextButton
v-if="hasWhatsappTemplates"
v-tooltip.top-end="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
icon="whatsapp"
color-scheme="secondary"
variant="smooth"
size="small"
:title="$t('CONVERSATION.FOOTER.WHATSAPP_TEMPLATES')"
icon="i-ph-whatsapp"
slate
faded
sm
@click="$emit('selectWhatsappTemplate')"
/>
<VideoCallButton
@@ -359,14 +356,13 @@ export default {
</h4>
</div>
</transition>
<woot-button
<NextButton
v-if="enableInsertArticleInReply"
v-tooltip.top-end="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
icon="document-text-link"
color-scheme="secondary"
variant="smooth"
size="small"
:title="$t('HELP_CENTER.ARTICLE_SEARCH.OPEN_ARTICLE_SEARCH')"
icon="i-ph-article-ny-times"
slate
faded
sm
@click="toggleInsertArticle"
/>
</div>
@@ -384,14 +380,6 @@ export default {
</template>
<style lang="scss" scoped>
.bottom-box {
@apply flex justify-between py-3 px-4;
&.is-note-mode {
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
.left-wrap {
@apply items-center flex gap-2;
}

View File

@@ -1,9 +1,15 @@
<script>
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
import NextButton from 'dashboard/components-next/button/Button.vue';
import EditorModeToggle from './EditorModeToggle.vue';
export default {
name: 'ReplyTopPanel',
components: {
NextButton,
EditorModeToggle,
},
props: {
mode: {
type: String,
@@ -17,10 +23,6 @@ export default {
type: Number,
default: () => 0,
},
popoutReplyBox: {
type: Boolean,
default: false,
},
},
emits: ['setReplyMode', 'togglePopout'],
setup(props, { emit }) {
@@ -33,6 +35,13 @@ export default {
const handleNoteClick = () => {
setReplyMode(REPLY_EDITOR_MODES.NOTE);
};
const handleModeToggle = () => {
const newMode =
props.mode === REPLY_EDITOR_MODES.REPLY
? REPLY_EDITOR_MODES.NOTE
: REPLY_EDITOR_MODES.REPLY;
setReplyMode(newMode);
};
const keyboardEvents = {
'Alt+KeyP': {
action: () => handleNoteClick(),
@@ -46,8 +55,10 @@ export default {
useKeyboardEvents(keyboardEvents);
return {
handleModeToggle,
handleReplyClick,
handleNoteClick,
REPLY_EDITOR_MODES,
};
},
computed: {
@@ -74,27 +85,12 @@ export default {
</script>
<template>
<div class="flex justify-between bg-black-50 dark:bg-slate-800">
<div class="button-group">
<woot-button
variant="clear"
class="button--reply"
:class="replyButtonClass"
@click="handleReplyClick"
>
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
</woot-button>
<woot-button
class="button--note"
variant="clear"
color-scheme="warning"
:class="noteButtonClass"
@click="handleNoteClick"
>
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
</woot-button>
</div>
<div class="flex justify-between h-[52px] gap-2 ltr:pl-3 rtl:pr-3">
<EditorModeToggle
:mode="mode"
class="mt-3"
@toggle-mode="handleModeToggle"
/>
<div class="flex items-center mx-4 my-0">
<div v-if="isMessageLengthReachingThreshold" class="text-xs">
<span :class="charLengthClass">
@@ -102,20 +98,10 @@ export default {
</span>
</div>
</div>
<woot-button
v-if="popoutReplyBox"
variant="clear"
icon="dismiss"
color-scheme="secondary"
class-names="popout-button"
@click="$emit('togglePopout')"
/>
<woot-button
v-else
variant="clear"
icon="resize-large"
color-scheme="secondary"
class-names="popout-button"
<NextButton
ghost
class="ltr:rounded-bl-md rtl:rounded-br-md ltr:rounded-br-none rtl:rounded-bl-none ltr:rounded-tl-none rtl:rounded-tr-none text-n-slate-11 ltr:rounded-tr-[11px] rtl:rounded-tl-[11px]"
icon="i-lucide-maximize-2"
@click="$emit('togglePopout')"
/>
</div>
@@ -127,28 +113,35 @@ export default {
.button {
@apply text-sm font-medium py-2.5 px-4 m-0 relative z-10;
&.is-active {
@apply bg-white dark:bg-slate-900;
}
}
.button--reply {
@apply border-r rounded-none border-b-0 border-l-0 border-t-0 border-slate-50 dark:border-slate-700;
&:hover,
&:focus {
@apply border-r border-slate-50 dark:border-slate-700;
}
}
.button--note {
@apply border-l-0 rounded-none;
&.is-active {
@apply border-r border-b-0 bg-yellow-100 dark:bg-yellow-800 border-t-0 border-slate-50 dark:border-slate-700;
}
&:hover,
&:active {
@apply text-yellow-700 dark:text-yellow-700;
}
}
}
.button--note {
@apply text-yellow-600 dark:text-yellow-600 bg-transparent dark:bg-transparent;
}

View File

@@ -623,7 +623,11 @@ export default {
}
.reply-box {
@apply border border-solid border-slate-75 dark:border-slate-600 max-w-[75rem] w-[70%];
@apply border border-n-weak max-w-[75rem] w-[70%];
&.is-private {
@apply dark:border-n-amber-3/30 border-n-amber-12/5;
}
}
.reply-box .reply-box__top {

View File

@@ -1242,28 +1242,12 @@ export default {
}
.reply-box {
transition:
box-shadow 0.35s cubic-bezier(0.37, 0, 0.63, 1),
height 2s cubic-bezier(0.37, 0, 0.63, 1);
transition: height 2s cubic-bezier(0.37, 0, 0.63, 1);
@apply relative border-t border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900;
&.is-focused {
box-shadow:
0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
@apply relative mb-2 mx-2 border border-n-weak rounded-xl bg-n-solid-1;
&.is-private {
@apply bg-yellow-100 dark:bg-yellow-800;
.reply-box__top {
@apply bg-yellow-100 dark:bg-yellow-800;
> input {
@apply bg-yellow-100 dark:bg-yellow-800;
}
}
@apply bg-n-solid-amber dark:border-n-amber-3/10 border-n-amber-12/5;
}
}
@@ -1272,7 +1256,7 @@ export default {
}
.reply-box__top {
@apply relative py-0 px-4 -mt-px border-t border-solid border-slate-50 dark:border-slate-700;
@apply relative py-0 px-4 -mt-px;
textarea {
@apply shadow-none border-transparent bg-transparent m-0 max-h-60 min-h-[3rem] pt-4 pb-0 px-0 resize-none;

View File

@@ -89,11 +89,11 @@ export default {
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.TO') }}
</label>
<div class="rounded-none flex-1 min-w-0 m-0 whitespace-nowrap">
<div class="flex-1 min-w-0 m-0 rounded-none whitespace-nowrap">
<woot-input
v-model="v$.toEmailsVal.$model"
type="text"
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none"
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none [&>input]:!bg-transparent dark:[&>input]:!bg-transparent"
:class="{ error: v$.toEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@blur="onBlur"
@@ -106,10 +106,10 @@ export default {
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.LABEL') }}
</label>
<div class="rounded-none flex-1 min-w-0 m-0 whitespace-nowrap">
<div class="flex-1 min-w-0 m-0 rounded-none whitespace-nowrap">
<woot-input
v-model="v$.ccEmailsVal.$model"
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none"
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none [&>input]:!bg-transparent dark:[&>input]:!bg-transparent"
type="text"
:class="{ error: v$.ccEmailsVal.$error }"
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
@@ -134,11 +134,11 @@ export default {
<label class="input-group-label">
{{ $t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.LABEL') }}
</label>
<div class="rounded-none flex-1 min-w-0 m-0 whitespace-nowrap">
<div class="flex-1 min-w-0 m-0 rounded-none whitespace-nowrap">
<woot-input
v-model="v$.bccEmailsVal.$model"
type="text"
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none"
class="[&>input]:!mb-0 [&>input]:border-transparent [&>input]:h-8 [&>input]:text-sm [&>input]:!border-0 [&>input]:border-none [&>input]:!bg-transparent dark:[&>input]:!bg-transparent"
:class="{ error: v$.bccEmailsVal.$error }"
:placeholder="
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')