Chat
Chat показывает переписку с поддержкой или модератором: сообщения пузырями с именем автора и временем, свои — у правого края; между днями стоит метка дня, перед первым непрочитанным — надпись «новые сообщения». Внизу поле ввода и кнопка отправки; Enter отправляет, Shift+Enter переносит строку. Загрузка истории, редактирование и вложения — у продукта.
Статус: experimental · в системе с 2026-09 · интерактивность: always (всегда)
Когда использовать
- Переписка по заявке, тикету или модерации внутри карточки сущности.
Не использовать:
- Односторонние уведомления — это лента событий или Toast.
- Одно сообщение с ответом — это форма с Textarea.
Альтернативы:
Переписка с поддержкой
Два дня сообщений, надпись «новые сообщения» и отправка своего.
Вчера
Поддержка Добрый день! Кампания прошла модерацию.
Спасибо, а лимит по GEO можно поднять?
Сегодня
Новые сообщения
Поддержка Да, лимит увеличен до $500 в день.
<!--
🟢
Переписка с поддержкой: два дня сообщений, надпись «новые сообщения» перед
непрочитанным и отправка своего сообщения полем внизу.
-->
<script>
import { t } from "$lib/spec/demo-lang.svelte.js";
import Chat from "$lib/ui/Chat.svelte";
let messages = $state([
{ id: 1, day: t("Вчера"), author: t("Поддержка"), text: t("Добрый день! Кампания прошла модерацию."), time: "10:12" },
{ id: 2, day: t("Вчера"), own: true, text: t("Спасибо, а лимит по GEO можно поднять?"), time: "10:40" },
{ id: 3, day: t("Сегодня"), author: t("Поддержка"), text: t("Да, лимит увеличен до $500 в день."), time: "09:05", unread: true },
]);
let value = $state("");
</script>
<div style="width: 420px;">
<Chat bind:value {messages} onsend={(text) => messages.push({ id: Date.now(), day: t("Сегодня"), own: true, text, time: t("сейчас") })} />
</div>
Вложения и правка
attachable с превью в поле, editable с меню у своих сообщений, вложения в пузырях с просмотром.
Сегодня
Поддержка Здравствуйте! Пришлите скриншот ошибки.
Вот он.
Новые сообщения
Поддержка Спасибо, смотрим.
<!--
🟢
Вложения и правка (эталон: Vue-легаси sections/chat с image,
edit-message, delete-message): картинки в пузырях с просмотром, меню у
своих сообщений, кнопка вложения с превью в поле.
-->
<script>
import { onDestroy } from "svelte";
import { t } from "$lib/spec/demo-lang.svelte.js";
import Chat from "$lib/ui/Chat.svelte";
const picture =
"data:image/svg+xml;utf8," +
encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="240" height="120"><rect width="240" height="120" fill="#dae6fd"/><circle cx="60" cy="60" r="36" fill="#ed7f6e"/></svg>');
let messages = $state([
{ id: 1, author: t("Поддержка"), text: t("Здравствуйте! Пришлите скриншот ошибки."), time: "10:02", day: t("Сегодня") },
{ id: 2, author: t("Вы"), own: true, text: t("Вот он."), attachments: [{ url: picture, alt: t("Скриншот") }], time: "10:05" },
{ id: 3, author: t("Поддержка"), text: t("Спасибо, смотрим."), time: "10:06", unread: true },
]);
let value = $state("");
let image = $state(null);
const uploads = new Set();
onDestroy(() => {
for (const url of uploads) URL.revokeObjectURL(url);
});
function send(text, file) {
const url = file ? URL.createObjectURL(file) : null;
if (url) uploads.add(url);
messages = [...messages, { id: Date.now(), author: t("Вы"), own: true, text, time: "10:10", attachments: url ? [{ url }] : [] }];
}
function edit({ id, text }) {
messages = messages.map((m) => (m.id === id ? { ...m, text } : m));
}
function remove(id) {
for (const { url } of messages.find((message) => message.id === id)?.attachments ?? []) {
if (uploads.delete(url)) URL.revokeObjectURL(url);
}
messages = messages.filter((m) => m.id !== id);
}
</script>
<div style="width: 480px;">
<Chat {messages} bind:value bind:image attachable editable onsend={send} onedit={edit} ondelete={remove} />
</div>