109 lines
2.6 KiB
Vue
109 lines
2.6 KiB
Vue
<template>
|
|
<div v-if="typingUsers.length > 0" class="px-4.5">
|
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
|
<div class="flex items-center gap-1">
|
|
<div class="typing-dots">
|
|
<span></span>
|
|
<span></span>
|
|
<span></span>
|
|
</div>
|
|
<component :is="typingMessage" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useTyping } from "@/composables/realtime";
|
|
import { useUserStore } from "@/stores/user";
|
|
import { computed, h, onBeforeUnmount } from "vue";
|
|
import UserAvatar from "./UserAvatar.vue";
|
|
|
|
const props = defineProps({
|
|
ticketId: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
});
|
|
|
|
const { typingUsers, cleanup } = useTyping(props.ticketId);
|
|
|
|
const typingMessage = computed(() => {
|
|
const count = typingUsers.length;
|
|
if (count === 0) return null;
|
|
const { getUser } = useUserStore();
|
|
let firstUser = getUser(typingUsers[0])?.full_name || typingUsers[0];
|
|
|
|
if (count === 1) {
|
|
return h("div", { class: "flex items-center gap-1" }, [
|
|
h(UserAvatar, { name: firstUser, size: "sm" }),
|
|
h("span", { class: "text-ink-gray-6 font-medium" }, firstUser),
|
|
h("span", { class: "text-ink-gray-5" }, " is typing..."),
|
|
]);
|
|
} else if (count === 2) {
|
|
return h("div", { class: "flex items-center gap-1" }, [
|
|
h("span", { class: "text-ink-gray-6 font-medium" }, firstUser),
|
|
h("span", { class: "text-ink-gray-5" }, " and "),
|
|
h(
|
|
"span",
|
|
{ class: "text-ink-gray-6 font-medium" },
|
|
getUser(typingUsers[1])?.full_name
|
|
),
|
|
h("span", { class: "text-ink-gray-5" }, " are typing..."),
|
|
]);
|
|
} else {
|
|
return h("div", { class: "flex items-center gap-1" }, [
|
|
h("span", { class: "text-ink-gray-6 font-medium" }, firstUser),
|
|
h("span", { class: "text-ink-gray-5" }, " and "),
|
|
h(
|
|
"span",
|
|
{ class: "text-ink-gray-6 font-medium" },
|
|
`${count - 1} others`
|
|
),
|
|
h("span", { class: "text-ink-gray-5" }, " are typing..."),
|
|
]);
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
cleanup();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.typing-dots {
|
|
display: inline-flex;
|
|
gap: 2px;
|
|
align-items: center;
|
|
}
|
|
|
|
.typing-dots span {
|
|
height: 4px;
|
|
width: 4px;
|
|
background-color: #6b7280;
|
|
border-radius: 50%;
|
|
animation: typing 1.4s infinite ease-in-out;
|
|
}
|
|
|
|
.typing-dots span:nth-child(1) {
|
|
animation-delay: -0.32s;
|
|
}
|
|
|
|
.typing-dots span:nth-child(2) {
|
|
animation-delay: -0.16s;
|
|
}
|
|
|
|
@keyframes typing {
|
|
0%,
|
|
80%,
|
|
100% {
|
|
transform: scale(0);
|
|
opacity: 0.5;
|
|
}
|
|
40% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
</style>
|