Files
chad/new-client/src/widgets/chat/ui/ChatMessages.vue
2026-05-23 22:49:52 +06:00

120 lines
2.7 KiB
Vue

<template>
<div class="chat-messages">
<div v-if="messageGroups.length" ref="scroll" class="chat-messages__list">
<ChatMessageGroup
v-for="group in messageGroups"
:key="`group-${group.messages[0].id}`"
:group="group"
/>
</div>
<ChadSpinner v-if="isFetching" class="chat-messages__spinner" />
<div v-if="!arrivedState.start" class="chat-messages__floating-actions">
<div v-if="hasUnreadMessages" class="chat-messages__has-unread-messages">
New messages
</div>
<ChadToBottom
class="chat-messages__scroll-to-bottom"
@click="scrollToStart()"
/>
</div>
</div>
</template>
<script setup lang="ts">
import ChadSpinner from '@shared/components/ui/Spinner.vue'
import ChadToBottom from '@shared/components/ui/ToBottom.vue'
import { useEventBus } from '@shared/composables/use-event-bus.ts'
import { useChatScroll } from '@widgets/chat/composables/use-chat-scroll.ts'
import ChatMessageGroup from '@widgets/chat/ui/ChatMessageGroup.vue'
import { onUnmounted, ref, useTemplateRef, watch } from 'vue'
import { useChatHistory } from '../composables/use-chat-history'
const eventBus = useEventBus()
const scrollRef = useTemplateRef('scroll')
const { messageGroups, hasMoreMessages, loadMoreMessages, isFetching } = useChatHistory()
const hasUnreadMessages = ref(false)
const { arrivedState, scrollToStart } = useChatScroll(scrollRef, {
startOffset: 100,
endOffset: 100,
})
watch(() => arrivedState.end, (arrived) => {
if (!arrived)
return
if (hasMoreMessages.value) {
loadMoreMessages()
}
})
watch(() => arrivedState.start, () => {
hasUnreadMessages.value = false
})
eventBus.on('chat:new-message', onNewChatMessage)
onUnmounted(() => {
eventBus.off('chat:new-message', onNewChatMessage)
})
function onNewChatMessage() {
if (!arrivedState.start) {
hasUnreadMessages.value = true
}
}
</script>
<style lang="scss">
.chat-messages {
position: relative;
overflow: hidden;
&__spinner {
position: absolute;
top: var(--space-4);
left: 50%;
translate: -50% 0;
z-index: 1;
}
&__list {
overflow-x: hidden;
overflow-y: auto;
overflow-y: overlay;
overflow-anchor: none;
position: relative;
max-height: 100%;
}
&__floating-actions {
position: absolute;
display: inline-flex;
align-items: flex-end;
gap: var(--space-2);
bottom: var(--space-4);
right: var(--space-4);
pointer-events: none;
}
&__has-unread-messages {
@include font-micro;
background-color: var(--yellow);
padding: var(--space-1) var(--space-2);
box-shadow: var(--shadow-sm);
}
&__scroll-to-bottom {
pointer-events: auto;
}
}
</style>