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