@progress/kendo-react-conversational-ui
Version:
React Chat component allows the user to participate in chat sessions with users or chat bots. KendoReact Conversational UI components
281 lines (280 loc) • 10.2 kB
JavaScript
/**
* @license
*-------------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the package root for more information
*-------------------------------------------------------------------------------------------
*/
import * as t from "react";
import { classNames as L, IconWrap as J } from "@progress/kendo-react-common";
import { Button as Q } from "@progress/kendo-react-buttons";
import { downloadIcon as be, chevronUpIcon as ge, chevronDownIcon as ke, checkCircleIcon as Ce, undoIcon as ye, copyIcon as Ee } from "@progress/kendo-svg-icons";
import { useLocalization as xe, useInternationalization as he } from "@progress/kendo-react-intl";
import { ContextMenu as Ie, MenuItem as we } from "@progress/kendo-react-layout";
import { copyToClipboard as Me, getDeletedMessageText as ve, getMessageById as Te, isAuthor as z, convertTextToLinkedContent as Z, scrollToMessageById as De } from "../utils.mjs";
import { useChatContext as Ne } from "./ChatContext.mjs";
import Ae from "./elements/FileBox.mjs";
import { downloadAllFiles as _, messages as I, downloadAll as ee, collapseMessage as te, expandMessage as ne } from "../../messages/index.mjs";
const Se = t.forwardRef(
(ae, i) => {
const {
item: n,
template: u,
contentTemplate: w,
onRequestSelection: m,
tabIndex: se = -1,
dateFormat: K = "g",
selected: l,
allowMessageCollapse: d,
messageToolbarActions: f,
messageContextMenuActions: p,
fileActions: B,
isSender: b
} = ae, {
setReplyToMessage: g,
messages: P,
user: c,
internalScrollContainerRef: k,
onToolbarAction: M,
onContextMenuAction: v,
onFileAction: W,
onDownload: r,
setShowCopyNotification: C,
statusTemplate: T,
timestampVisibility: D = "onFocus"
} = Ne(), [H, N] = t.useState(!1), [O, A] = t.useState(!1), [y, le] = t.useState(!0), [oe, U] = t.useState(!1), o = xe(), q = he(), X = t.useRef({ left: 0, top: 0 }), E = t.useCallback(() => {
n != null && n.text && Me(n.text), C && (C(!0), setTimeout(() => {
C(!1);
}, 3e3));
}, [n, C]), S = t.useCallback((e) => {
e.stopPropagation(), le((a) => !a);
}, []), re = t.useCallback(() => {
U(!1);
}, []), ce = t.useCallback((e) => {
(e.key === "Tab" || e.key === "Enter" || e.key === " " || e.key === "ArrowUp" || e.key === "ArrowDown") && U(!0);
}, []), F = t.useRef(null);
t.useEffect(() => {
l && F.current && F.current.focus();
}, [l]);
const R = t.useCallback((e) => {
X.current = { left: e.pageX, top: e.pageY }, e.preventDefault(), A(!0), N(!0);
}, []), Y = t.useCallback(() => {
A(!1), N(!1);
}, []), x = t.useCallback(
(e, a) => e.isDeleted ? ve(a, o) : e.text ? e.text : "",
[o]
), $ = t.useCallback(
(e) => {
var a;
(a = e.item) != null && a.text && (e.item.data.id === "copy" && !n.isDeleted ? E() : e.item.data.id === "reply" && g(n), v && v(e.item.data, e, n)), A(!1), N(!1);
},
[n, v, g, E]
), j = t.useCallback(() => {
if (n.replyToId) {
const e = Te(P, n.replyToId);
if (e) {
const a = (s) => {
s.preventDefault(), s.stopPropagation();
const h = k == null ? void 0 : k.current;
De(e.id, h);
};
return /* @__PURE__ */ t.createElement(
"div",
{
className: L(
"k-message-reference",
z(c, e) ? "k-message-reference-sender" : "k-message-reference-receiver"
),
onClick: a
},
/* @__PURE__ */ t.createElement("div", { className: "k-message-reference-content" }, Z(
x(e, z(c, e))
))
);
}
}
return null;
}, [n.replyToId, P, c, x, k]), V = t.useCallback(() => {
const e = [
{
id: "reply",
label: "Reply",
svgIcon: ye
},
{
id: "copy",
label: "Copy",
svgIcon: Ee
}
], a = p ? [
...e.map((s) => p.find(
(pe) => pe.id === s.id
) || s),
...p.filter(
(s) => !e.some((h) => h.id === s.id)
)
] : e;
return /* @__PURE__ */ t.createElement(
Ie,
{
onSelect: $,
onClose: Y,
vertical: !0,
show: H,
offset: X.current
},
a.map(
(s) => s.id === "delete" && !b ? null : /* @__PURE__ */ t.createElement(
we,
{
key: s.id,
text: s.label,
icon: s.icon,
svgIcon: s.svgIcon,
data: s
}
)
)
);
}, [p, $, Y, H, b]), G = t.useCallback(() => n.files && n.files.length > 1 ? /* @__PURE__ */ t.createElement("div", { className: "k-chat-download-button-wrapper" }, /* @__PURE__ */ t.createElement(
Q,
{
className: "k-chat-download-button",
fillMode: "flat",
themeColor: "base",
svgIcon: be,
onClick: (e) => {
e.stopPropagation(), r && r(n.files, n);
},
"aria-label": o.toLanguageString(
_,
I[_]
)
},
/* @__PURE__ */ t.createElement("span", { className: "k-button-text" }, o.toLanguageString(ee, I[ee]))
)) : null, [n, r, o]), ie = t.useCallback(() => {
if (n.typing)
return /* @__PURE__ */ t.createElement("div", { className: "k-chat-bubble k-bubble" }, /* @__PURE__ */ t.createElement("div", { className: "k-bubble-content" }, /* @__PURE__ */ t.createElement("div", { className: "k-typing-indicator" }, /* @__PURE__ */ t.createElement("span", null), /* @__PURE__ */ t.createElement("span", null), /* @__PURE__ */ t.createElement("span", null))));
if (u) {
const e = u;
return /* @__PURE__ */ t.createElement(t.Fragment, null, /* @__PURE__ */ t.createElement(e, { item: n }), V());
} else if (n.text || n.files && n.files.length > 0)
return /* @__PURE__ */ t.createElement(t.Fragment, null, /* @__PURE__ */ t.createElement(
"div",
{
className: L("k-chat-bubble", "k-bubble", {
"k-bubble-expandable": d,
"k-expanded": d && y,
"k-selected": l,
"k-active": O
}),
onClick: () => m(l ? void 0 : n.selectionIndex),
onContextMenu: R
},
/* @__PURE__ */ t.createElement("div", { className: "k-bubble-content" }, w ? /* @__PURE__ */ t.createElement(w, { item: n }) : /* @__PURE__ */ t.createElement(t.Fragment, null, (n.text || n.isDeleted || n.replyToId) && /* @__PURE__ */ t.createElement("span", { className: "k-chat-bubble-text" }, !n.isDeleted && j(), Z(
x(n, z(c, n))
)), n.files && n.files.length > 0 && /* @__PURE__ */ t.createElement(
Ae,
{
files: n.files,
message: n,
onFileAction: W,
onDownload: (e) => r == null ? void 0 : r(e, n),
renderInTextarea: !1,
fileActions: B
}
), G())),
d && /* @__PURE__ */ t.createElement(
"span",
{
className: "k-bubble-expandable-indicator",
onClick: S,
role: "button",
"aria-label": y ? o.toLanguageString(
te,
I[te]
) : o.toLanguageString(
ne,
I[ne]
),
tabIndex: 0,
onKeyDown: (e) => {
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), S(e));
}
},
/* @__PURE__ */ t.createElement(J, { icon: y ? ge : ke, size: "medium" })
)
), V());
return null;
}, [
n,
u,
w,
d,
y,
l,
O,
m,
R,
j,
x,
c,
W,
B,
G,
S,
o,
V,
r
]), ue = t.useCallback(() => {
let e = null;
if (n.timestamp) {
const a = D === "onFocus" && l;
D === "hidden" || (e = /* @__PURE__ */ t.createElement("time", { className: "k-message-time", "aria-hidden": !a }, q.formatDate(n.timestamp, K)));
}
return e;
}, [n.timestamp, l, K, D, q]), me = t.useCallback(() => {
let e = null;
return n.status && l && (T ? e = t.createElement(T, { item: n }) : e = /* @__PURE__ */ t.createElement("span", { className: "k-message-status" }, /* @__PURE__ */ t.createElement(J, { icon: Ce, size: "xsmall" }), n.status)), e;
}, [n, l, T]), de = t.useCallback(() => L({ "k-focus": l, "k-message-removed": n.isDeleted }, "k-message"), [l, n.isDeleted]), fe = t.useCallback(() => f && f.length > 0 && /* @__PURE__ */ t.createElement("div", { className: "k-chat-message-toolbar k-toolbar k-toolbar-md k-toolbar-flat" }, f.map((e) => e.id === "delete" && !b ? null : /* @__PURE__ */ t.createElement(
Q,
{
key: e.id,
fillMode: "flat",
className: "k-toolbar-button",
themeColor: "base",
icon: e.icon,
svgIcon: e.svgIcon,
onClick: (a) => {
a.stopPropagation(), e.id === "copy" && !n.isDeleted ? E() : e.id === "reply" && g(n), M && M(e, a, n);
},
onMouseDown: (a) => a.preventDefault()
}
))), [E, b, n, f, M, g]);
return /* @__PURE__ */ t.createElement(
"div",
{
"data-message-id": n.id,
className: de(),
tabIndex: se,
onMouseDown: re,
onKeyDown: ce,
onFocus: (e) => {
oe && m(n.selectionIndex);
},
ref: (e) => {
F.current = e, typeof i == "function" ? i(e) : i && (i.current = e);
},
onContextMenu: u ? R : void 0
},
ue(),
ie(),
me(),
fe()
);
}
);
Se.displayName = "ChatMessage";
export {
Se as ChatMessage
};