@yext/chat-ui-react 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/package.json.js +1 -1
- package/lib/commonjs/src/components/ChatPanel.d.ts.map +1 -1
- package/lib/commonjs/src/components/ChatPanel.js +23 -6
- package/lib/commonjs/src/components/ChatPanel.js.map +1 -1
- package/lib/esm/package.json.mjs +1 -1
- package/lib/esm/src/components/ChatPanel.d.ts.map +1 -1
- package/lib/esm/src/components/ChatPanel.mjs +24 -7
- package/lib/esm/src/components/ChatPanel.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ChatPanel.tsx +34 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/ChatPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/ChatPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,EAEL,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAa,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAO7E;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD;AAeD;;;;GAIG;AACH,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,CAAC,EAC9D,IAAI,CAAC,cAAc,EAAE,kBAAkB,CAAC;IAC1C,kDAAkD;IAClD,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACrB;;OAEG;IACH,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,qBAmF9C"}
|
|
@@ -55,20 +55,37 @@ function ChatPanel(props) {
|
|
|
55
55
|
const res = stream ? chat.streamNextMessage() : chat.getNextMessage();
|
|
56
56
|
res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));
|
|
57
57
|
}, [chat, props, messages, defaultHandleApiError, canSendMessage]);
|
|
58
|
-
const messagesRef = React.useRef(
|
|
59
|
-
|
|
58
|
+
const messagesRef = React.useRef([]);
|
|
59
|
+
const messagesContainer = React.useRef(null);
|
|
60
|
+
// Handle scrolling when messages change
|
|
60
61
|
React.useEffect(() => {
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
let scrollTop = 0;
|
|
63
|
+
messagesRef.current = messagesRef.current.slice(0, messages.length);
|
|
64
|
+
// Sums up scroll heights of all messages except the last one
|
|
65
|
+
if (messagesRef?.current.length > 1) {
|
|
66
|
+
scrollTop = messagesRef.current
|
|
67
|
+
.slice(0, -1)
|
|
68
|
+
.map((elem, _) => elem?.scrollHeight ?? 0)
|
|
69
|
+
.reduce((total, height) => total + height);
|
|
70
|
+
}
|
|
71
|
+
// Scroll to the top of the last message
|
|
72
|
+
messagesContainer.current?.scroll({
|
|
73
|
+
top: scrollTop,
|
|
63
74
|
behavior: "smooth",
|
|
64
75
|
});
|
|
65
76
|
}, [messages]);
|
|
77
|
+
const setMessagesRef = React.useCallback((index) => {
|
|
78
|
+
if (!messagesRef?.current)
|
|
79
|
+
return null;
|
|
80
|
+
return (message) => (messagesRef.current[index] = message);
|
|
81
|
+
}, []);
|
|
66
82
|
return (React__default.default.createElement("div", { className: "yext-chat w-full h-full" },
|
|
67
83
|
React__default.default.createElement("div", { className: cssClasses.container },
|
|
68
84
|
header,
|
|
69
85
|
React__default.default.createElement("div", { className: cssClasses.messagesScrollContainer },
|
|
70
|
-
React__default.default.createElement("div", { ref:
|
|
71
|
-
messages.map((message, index) => (React__default.default.createElement(
|
|
86
|
+
React__default.default.createElement("div", { ref: messagesContainer, className: cssClasses.messagesContainer },
|
|
87
|
+
messages.map((message, index) => (React__default.default.createElement("div", { key: index, ref: setMessagesRef(index) },
|
|
88
|
+
React__default.default.createElement(MessageBubble.MessageBubble, { ...props, customCssClasses: cssClasses.messageBubbleCssClasses, message: message })))),
|
|
72
89
|
loading && React__default.default.createElement(LoadingDots.LoadingDots, null))),
|
|
73
90
|
React__default.default.createElement("div", { className: cssClasses.inputContainer },
|
|
74
91
|
React__default.default.createElement(ChatInput.ChatInput, { ...props, customCssClasses: cssClasses.inputCssClasses })))));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPanel.js","sources":["../../../../src/components/ChatPanel.tsx"],"sourcesContent":["import React, { useEffect, useRef } from \"react\";\nimport { useChatState, useChatActions } from \"@yext/chat-headless-react\";\nimport {\n MessageBubble,\n MessageBubbleCssClasses,\n MessageBubbleProps,\n} from \"./MessageBubble\";\nimport { ChatInput, ChatInputCssClasses, ChatInputProps } from \"./ChatInput\";\nimport { LoadingDots } from \"./LoadingDots\";\nimport { useComposedCssClasses } from \"../hooks\";\nimport { useDefaultHandleApiError } from \"../hooks/useDefaultHandleApiError\";\nimport { withStylelessCssClasses } from \"../utils/withStylelessCssClasses\";\nimport { useReportAnalyticsEvent } from \"../hooks/useReportAnalyticsEvent\";\n\n/**\n * The CSS class interface for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelCssClasses {\n container?: string;\n messagesContainer?: string;\n messagesScrollContainer?: string;\n inputContainer?: string;\n inputCssClasses?: ChatInputCssClasses;\n messageBubbleCssClasses?: MessageBubbleCssClasses;\n}\n\nconst builtInCssClasses: ChatPanelCssClasses = withStylelessCssClasses(\n \"Panel\",\n {\n container: \"h-full w-full flex flex-col relative shadow-2xl bg-white\",\n messagesScrollContainer: \"flex flex-col mt-auto overflow-hidden\",\n messagesContainer: \"flex flex-col gap-y-1 px-4 overflow-auto\",\n inputContainer: \"w-full p-4\",\n messageBubbleCssClasses: {\n topContainer: \"first:mt-4\",\n },\n }\n);\n\n/**\n * The props for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelProps\n extends Omit<MessageBubbleProps, \"customCssClasses\" | \"message\">,\n Omit<ChatInputProps, \"customCssClasses\"> {\n /** A header to render at the top of the panel. */\n header?: JSX.Element;\n /**\n * CSS classes for customizing the component styling.\n */\n customCssClasses?: ChatPanelCssClasses;\n}\n\n/**\n * A component that renders a full panel for chat bot interactions. This includes\n * the message bubbles for the conversation, input box with send button, and header\n * (if provided).\n *\n * @public\n *\n * @param props - {@link ChatPanelProps}\n */\nexport function ChatPanel(props: ChatPanelProps) {\n const { header, customCssClasses } = props;\n const chat = useChatActions();\n const messages = useChatState((state) => state.conversation.messages);\n const loading = useChatState((state) => state.conversation.isLoading);\n const canSendMessage = useChatState(\n (state) => state.conversation.canSendMessage\n );\n const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);\n const defaultHandleApiError = useDefaultHandleApiError();\n const reportAnalyticsEvent = useReportAnalyticsEvent();\n\n useEffect(() => {\n reportAnalyticsEvent({\n action: \"CHAT_IMPRESSION\",\n });\n }, [reportAnalyticsEvent]);\n\n // Fetch the first message on load, if there are no existing messages or a request being processed\n useEffect(() => {\n if (messages.length !== 0 || !canSendMessage) {\n return;\n }\n const { stream = false, handleError } = props;\n const res = stream ? chat.streamNextMessage() : chat.getNextMessage();\n res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));\n }, [chat, props, messages, defaultHandleApiError, canSendMessage]);\n\n const messagesRef = useRef<HTMLDivElement>(null);\n\n //
|
|
1
|
+
{"version":3,"file":"ChatPanel.js","sources":["../../../../src/components/ChatPanel.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef } from \"react\";\nimport { useChatState, useChatActions } from \"@yext/chat-headless-react\";\nimport {\n MessageBubble,\n MessageBubbleCssClasses,\n MessageBubbleProps,\n} from \"./MessageBubble\";\nimport { ChatInput, ChatInputCssClasses, ChatInputProps } from \"./ChatInput\";\nimport { LoadingDots } from \"./LoadingDots\";\nimport { useComposedCssClasses } from \"../hooks\";\nimport { useDefaultHandleApiError } from \"../hooks/useDefaultHandleApiError\";\nimport { withStylelessCssClasses } from \"../utils/withStylelessCssClasses\";\nimport { useReportAnalyticsEvent } from \"../hooks/useReportAnalyticsEvent\";\n\n/**\n * The CSS class interface for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelCssClasses {\n container?: string;\n messagesContainer?: string;\n messagesScrollContainer?: string;\n inputContainer?: string;\n inputCssClasses?: ChatInputCssClasses;\n messageBubbleCssClasses?: MessageBubbleCssClasses;\n}\n\nconst builtInCssClasses: ChatPanelCssClasses = withStylelessCssClasses(\n \"Panel\",\n {\n container: \"h-full w-full flex flex-col relative shadow-2xl bg-white\",\n messagesScrollContainer: \"flex flex-col mt-auto overflow-hidden\",\n messagesContainer: \"flex flex-col gap-y-1 px-4 overflow-auto\",\n inputContainer: \"w-full p-4\",\n messageBubbleCssClasses: {\n topContainer: \"first:mt-4\",\n },\n }\n);\n\n/**\n * The props for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelProps\n extends Omit<MessageBubbleProps, \"customCssClasses\" | \"message\">,\n Omit<ChatInputProps, \"customCssClasses\"> {\n /** A header to render at the top of the panel. */\n header?: JSX.Element;\n /**\n * CSS classes for customizing the component styling.\n */\n customCssClasses?: ChatPanelCssClasses;\n}\n\n/**\n * A component that renders a full panel for chat bot interactions. This includes\n * the message bubbles for the conversation, input box with send button, and header\n * (if provided).\n *\n * @public\n *\n * @param props - {@link ChatPanelProps}\n */\nexport function ChatPanel(props: ChatPanelProps) {\n const { header, customCssClasses } = props;\n const chat = useChatActions();\n const messages = useChatState((state) => state.conversation.messages);\n const loading = useChatState((state) => state.conversation.isLoading);\n const canSendMessage = useChatState(\n (state) => state.conversation.canSendMessage\n );\n const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);\n const defaultHandleApiError = useDefaultHandleApiError();\n const reportAnalyticsEvent = useReportAnalyticsEvent();\n\n useEffect(() => {\n reportAnalyticsEvent({\n action: \"CHAT_IMPRESSION\",\n });\n }, [reportAnalyticsEvent]);\n\n // Fetch the first message on load, if there are no existing messages or a request being processed\n useEffect(() => {\n if (messages.length !== 0 || !canSendMessage) {\n return;\n }\n const { stream = false, handleError } = props;\n const res = stream ? chat.streamNextMessage() : chat.getNextMessage();\n res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));\n }, [chat, props, messages, defaultHandleApiError, canSendMessage]);\n\n const messagesRef = useRef<Array<HTMLDivElement | null>>([]);\n const messagesContainer = useRef<HTMLDivElement>(null);\n\n // Handle scrolling when messages change\n useEffect(() => {\n let scrollTop = 0;\n messagesRef.current = messagesRef.current.slice(0, messages.length);\n\n // Sums up scroll heights of all messages except the last one\n if (messagesRef?.current.length > 1) {\n scrollTop = messagesRef.current\n .slice(0, -1)\n .map((elem, _) => elem?.scrollHeight ?? 0)\n .reduce((total, height) => total + height);\n }\n\n // Scroll to the top of the last message\n messagesContainer.current?.scroll({\n top: scrollTop,\n behavior: \"smooth\",\n });\n }, [messages]);\n\n const setMessagesRef = useCallback((index) => {\n if (!messagesRef?.current) return null;\n return (message) => (messagesRef.current[index] = message);\n }, []);\n\n return (\n <div className=\"yext-chat w-full h-full\">\n <div className={cssClasses.container}>\n {header}\n <div className={cssClasses.messagesScrollContainer}>\n <div\n ref={messagesContainer}\n className={cssClasses.messagesContainer}\n >\n {messages.map((message, index) => (\n <div key={index} ref={setMessagesRef(index)}>\n <MessageBubble\n {...props}\n customCssClasses={cssClasses.messageBubbleCssClasses}\n message={message}\n />\n </div>\n ))}\n {loading && <LoadingDots />}\n </div>\n </div>\n <div className={cssClasses.inputContainer}>\n <ChatInput {...props} customCssClasses={cssClasses.inputCssClasses} />\n </div>\n </div>\n </div>\n );\n}\n"],"names":["withStylelessCssClasses","useChatActions","useChatState","useComposedCssClasses","useDefaultHandleApiError","useReportAnalyticsEvent","useEffect","useRef","useCallback","React","MessageBubble","LoadingDots","ChatInput"],"mappings":"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"}
|
package/lib/esm/package.json.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/ChatPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/ChatPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAE9D,OAAO,EAEL,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAa,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAO7E;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD;AAeD;;;;GAIG;AACH,MAAM,WAAW,cACf,SAAQ,IAAI,CAAC,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,CAAC,EAC9D,IAAI,CAAC,cAAc,EAAE,kBAAkB,CAAC;IAC1C,kDAAkD;IAClD,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACrB;;OAEG;IACH,gBAAgB,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,qBAmF9C"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useCallback } from 'react';
|
|
2
2
|
import { useChatActions, useChatState } from '@yext/chat-headless-react';
|
|
3
3
|
import { MessageBubble } from './MessageBubble.mjs';
|
|
4
4
|
import { ChatInput } from './ChatInput.mjs';
|
|
@@ -49,20 +49,37 @@ function ChatPanel(props) {
|
|
|
49
49
|
const res = stream ? chat.streamNextMessage() : chat.getNextMessage();
|
|
50
50
|
res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));
|
|
51
51
|
}, [chat, props, messages, defaultHandleApiError, canSendMessage]);
|
|
52
|
-
const messagesRef = useRef(
|
|
53
|
-
|
|
52
|
+
const messagesRef = useRef([]);
|
|
53
|
+
const messagesContainer = useRef(null);
|
|
54
|
+
// Handle scrolling when messages change
|
|
54
55
|
useEffect(() => {
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
let scrollTop = 0;
|
|
57
|
+
messagesRef.current = messagesRef.current.slice(0, messages.length);
|
|
58
|
+
// Sums up scroll heights of all messages except the last one
|
|
59
|
+
if (messagesRef?.current.length > 1) {
|
|
60
|
+
scrollTop = messagesRef.current
|
|
61
|
+
.slice(0, -1)
|
|
62
|
+
.map((elem, _) => elem?.scrollHeight ?? 0)
|
|
63
|
+
.reduce((total, height) => total + height);
|
|
64
|
+
}
|
|
65
|
+
// Scroll to the top of the last message
|
|
66
|
+
messagesContainer.current?.scroll({
|
|
67
|
+
top: scrollTop,
|
|
57
68
|
behavior: "smooth",
|
|
58
69
|
});
|
|
59
70
|
}, [messages]);
|
|
71
|
+
const setMessagesRef = useCallback((index) => {
|
|
72
|
+
if (!messagesRef?.current)
|
|
73
|
+
return null;
|
|
74
|
+
return (message) => (messagesRef.current[index] = message);
|
|
75
|
+
}, []);
|
|
60
76
|
return (React.createElement("div", { className: "yext-chat w-full h-full" },
|
|
61
77
|
React.createElement("div", { className: cssClasses.container },
|
|
62
78
|
header,
|
|
63
79
|
React.createElement("div", { className: cssClasses.messagesScrollContainer },
|
|
64
|
-
React.createElement("div", { ref:
|
|
65
|
-
messages.map((message, index) => (React.createElement(
|
|
80
|
+
React.createElement("div", { ref: messagesContainer, className: cssClasses.messagesContainer },
|
|
81
|
+
messages.map((message, index) => (React.createElement("div", { key: index, ref: setMessagesRef(index) },
|
|
82
|
+
React.createElement(MessageBubble, { ...props, customCssClasses: cssClasses.messageBubbleCssClasses, message: message })))),
|
|
66
83
|
loading && React.createElement(LoadingDots, null))),
|
|
67
84
|
React.createElement("div", { className: cssClasses.inputContainer },
|
|
68
85
|
React.createElement(ChatInput, { ...props, customCssClasses: cssClasses.inputCssClasses })))));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPanel.mjs","sources":["../../../../src/components/ChatPanel.tsx"],"sourcesContent":["import React, { useEffect, useRef } from \"react\";\nimport { useChatState, useChatActions } from \"@yext/chat-headless-react\";\nimport {\n MessageBubble,\n MessageBubbleCssClasses,\n MessageBubbleProps,\n} from \"./MessageBubble\";\nimport { ChatInput, ChatInputCssClasses, ChatInputProps } from \"./ChatInput\";\nimport { LoadingDots } from \"./LoadingDots\";\nimport { useComposedCssClasses } from \"../hooks\";\nimport { useDefaultHandleApiError } from \"../hooks/useDefaultHandleApiError\";\nimport { withStylelessCssClasses } from \"../utils/withStylelessCssClasses\";\nimport { useReportAnalyticsEvent } from \"../hooks/useReportAnalyticsEvent\";\n\n/**\n * The CSS class interface for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelCssClasses {\n container?: string;\n messagesContainer?: string;\n messagesScrollContainer?: string;\n inputContainer?: string;\n inputCssClasses?: ChatInputCssClasses;\n messageBubbleCssClasses?: MessageBubbleCssClasses;\n}\n\nconst builtInCssClasses: ChatPanelCssClasses = withStylelessCssClasses(\n \"Panel\",\n {\n container: \"h-full w-full flex flex-col relative shadow-2xl bg-white\",\n messagesScrollContainer: \"flex flex-col mt-auto overflow-hidden\",\n messagesContainer: \"flex flex-col gap-y-1 px-4 overflow-auto\",\n inputContainer: \"w-full p-4\",\n messageBubbleCssClasses: {\n topContainer: \"first:mt-4\",\n },\n }\n);\n\n/**\n * The props for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelProps\n extends Omit<MessageBubbleProps, \"customCssClasses\" | \"message\">,\n Omit<ChatInputProps, \"customCssClasses\"> {\n /** A header to render at the top of the panel. */\n header?: JSX.Element;\n /**\n * CSS classes for customizing the component styling.\n */\n customCssClasses?: ChatPanelCssClasses;\n}\n\n/**\n * A component that renders a full panel for chat bot interactions. This includes\n * the message bubbles for the conversation, input box with send button, and header\n * (if provided).\n *\n * @public\n *\n * @param props - {@link ChatPanelProps}\n */\nexport function ChatPanel(props: ChatPanelProps) {\n const { header, customCssClasses } = props;\n const chat = useChatActions();\n const messages = useChatState((state) => state.conversation.messages);\n const loading = useChatState((state) => state.conversation.isLoading);\n const canSendMessage = useChatState(\n (state) => state.conversation.canSendMessage\n );\n const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);\n const defaultHandleApiError = useDefaultHandleApiError();\n const reportAnalyticsEvent = useReportAnalyticsEvent();\n\n useEffect(() => {\n reportAnalyticsEvent({\n action: \"CHAT_IMPRESSION\",\n });\n }, [reportAnalyticsEvent]);\n\n // Fetch the first message on load, if there are no existing messages or a request being processed\n useEffect(() => {\n if (messages.length !== 0 || !canSendMessage) {\n return;\n }\n const { stream = false, handleError } = props;\n const res = stream ? chat.streamNextMessage() : chat.getNextMessage();\n res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));\n }, [chat, props, messages, defaultHandleApiError, canSendMessage]);\n\n const messagesRef = useRef<HTMLDivElement>(null);\n\n //
|
|
1
|
+
{"version":3,"file":"ChatPanel.mjs","sources":["../../../../src/components/ChatPanel.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef } from \"react\";\nimport { useChatState, useChatActions } from \"@yext/chat-headless-react\";\nimport {\n MessageBubble,\n MessageBubbleCssClasses,\n MessageBubbleProps,\n} from \"./MessageBubble\";\nimport { ChatInput, ChatInputCssClasses, ChatInputProps } from \"./ChatInput\";\nimport { LoadingDots } from \"./LoadingDots\";\nimport { useComposedCssClasses } from \"../hooks\";\nimport { useDefaultHandleApiError } from \"../hooks/useDefaultHandleApiError\";\nimport { withStylelessCssClasses } from \"../utils/withStylelessCssClasses\";\nimport { useReportAnalyticsEvent } from \"../hooks/useReportAnalyticsEvent\";\n\n/**\n * The CSS class interface for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelCssClasses {\n container?: string;\n messagesContainer?: string;\n messagesScrollContainer?: string;\n inputContainer?: string;\n inputCssClasses?: ChatInputCssClasses;\n messageBubbleCssClasses?: MessageBubbleCssClasses;\n}\n\nconst builtInCssClasses: ChatPanelCssClasses = withStylelessCssClasses(\n \"Panel\",\n {\n container: \"h-full w-full flex flex-col relative shadow-2xl bg-white\",\n messagesScrollContainer: \"flex flex-col mt-auto overflow-hidden\",\n messagesContainer: \"flex flex-col gap-y-1 px-4 overflow-auto\",\n inputContainer: \"w-full p-4\",\n messageBubbleCssClasses: {\n topContainer: \"first:mt-4\",\n },\n }\n);\n\n/**\n * The props for the {@link ChatPanel} component.\n *\n * @public\n */\nexport interface ChatPanelProps\n extends Omit<MessageBubbleProps, \"customCssClasses\" | \"message\">,\n Omit<ChatInputProps, \"customCssClasses\"> {\n /** A header to render at the top of the panel. */\n header?: JSX.Element;\n /**\n * CSS classes for customizing the component styling.\n */\n customCssClasses?: ChatPanelCssClasses;\n}\n\n/**\n * A component that renders a full panel for chat bot interactions. This includes\n * the message bubbles for the conversation, input box with send button, and header\n * (if provided).\n *\n * @public\n *\n * @param props - {@link ChatPanelProps}\n */\nexport function ChatPanel(props: ChatPanelProps) {\n const { header, customCssClasses } = props;\n const chat = useChatActions();\n const messages = useChatState((state) => state.conversation.messages);\n const loading = useChatState((state) => state.conversation.isLoading);\n const canSendMessage = useChatState(\n (state) => state.conversation.canSendMessage\n );\n const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses);\n const defaultHandleApiError = useDefaultHandleApiError();\n const reportAnalyticsEvent = useReportAnalyticsEvent();\n\n useEffect(() => {\n reportAnalyticsEvent({\n action: \"CHAT_IMPRESSION\",\n });\n }, [reportAnalyticsEvent]);\n\n // Fetch the first message on load, if there are no existing messages or a request being processed\n useEffect(() => {\n if (messages.length !== 0 || !canSendMessage) {\n return;\n }\n const { stream = false, handleError } = props;\n const res = stream ? chat.streamNextMessage() : chat.getNextMessage();\n res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));\n }, [chat, props, messages, defaultHandleApiError, canSendMessage]);\n\n const messagesRef = useRef<Array<HTMLDivElement | null>>([]);\n const messagesContainer = useRef<HTMLDivElement>(null);\n\n // Handle scrolling when messages change\n useEffect(() => {\n let scrollTop = 0;\n messagesRef.current = messagesRef.current.slice(0, messages.length);\n\n // Sums up scroll heights of all messages except the last one\n if (messagesRef?.current.length > 1) {\n scrollTop = messagesRef.current\n .slice(0, -1)\n .map((elem, _) => elem?.scrollHeight ?? 0)\n .reduce((total, height) => total + height);\n }\n\n // Scroll to the top of the last message\n messagesContainer.current?.scroll({\n top: scrollTop,\n behavior: \"smooth\",\n });\n }, [messages]);\n\n const setMessagesRef = useCallback((index) => {\n if (!messagesRef?.current) return null;\n return (message) => (messagesRef.current[index] = message);\n }, []);\n\n return (\n <div className=\"yext-chat w-full h-full\">\n <div className={cssClasses.container}>\n {header}\n <div className={cssClasses.messagesScrollContainer}>\n <div\n ref={messagesContainer}\n className={cssClasses.messagesContainer}\n >\n {messages.map((message, index) => (\n <div key={index} ref={setMessagesRef(index)}>\n <MessageBubble\n {...props}\n customCssClasses={cssClasses.messageBubbleCssClasses}\n message={message}\n />\n </div>\n ))}\n {loading && <LoadingDots />}\n </div>\n </div>\n <div className={cssClasses.inputContainer}>\n <ChatInput {...props} customCssClasses={cssClasses.inputCssClasses} />\n </div>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from "react";
|
|
1
|
+
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { useChatState, useChatActions } from "@yext/chat-headless-react";
|
|
3
3
|
import {
|
|
4
4
|
MessageBubble,
|
|
@@ -92,29 +92,51 @@ export function ChatPanel(props: ChatPanelProps) {
|
|
|
92
92
|
res.catch((e) => (handleError ? handleError(e) : defaultHandleApiError(e)));
|
|
93
93
|
}, [chat, props, messages, defaultHandleApiError, canSendMessage]);
|
|
94
94
|
|
|
95
|
-
const messagesRef = useRef<HTMLDivElement
|
|
95
|
+
const messagesRef = useRef<Array<HTMLDivElement | null>>([]);
|
|
96
|
+
const messagesContainer = useRef<HTMLDivElement>(null);
|
|
96
97
|
|
|
97
|
-
//
|
|
98
|
+
// Handle scrolling when messages change
|
|
98
99
|
useEffect(() => {
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
let scrollTop = 0;
|
|
101
|
+
messagesRef.current = messagesRef.current.slice(0, messages.length);
|
|
102
|
+
|
|
103
|
+
// Sums up scroll heights of all messages except the last one
|
|
104
|
+
if (messagesRef?.current.length > 1) {
|
|
105
|
+
scrollTop = messagesRef.current
|
|
106
|
+
.slice(0, -1)
|
|
107
|
+
.map((elem, _) => elem?.scrollHeight ?? 0)
|
|
108
|
+
.reduce((total, height) => total + height);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Scroll to the top of the last message
|
|
112
|
+
messagesContainer.current?.scroll({
|
|
113
|
+
top: scrollTop,
|
|
101
114
|
behavior: "smooth",
|
|
102
115
|
});
|
|
103
116
|
}, [messages]);
|
|
104
117
|
|
|
118
|
+
const setMessagesRef = useCallback((index) => {
|
|
119
|
+
if (!messagesRef?.current) return null;
|
|
120
|
+
return (message) => (messagesRef.current[index] = message);
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
105
123
|
return (
|
|
106
124
|
<div className="yext-chat w-full h-full">
|
|
107
125
|
<div className={cssClasses.container}>
|
|
108
126
|
{header}
|
|
109
127
|
<div className={cssClasses.messagesScrollContainer}>
|
|
110
|
-
<div
|
|
128
|
+
<div
|
|
129
|
+
ref={messagesContainer}
|
|
130
|
+
className={cssClasses.messagesContainer}
|
|
131
|
+
>
|
|
111
132
|
{messages.map((message, index) => (
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
<div key={index} ref={setMessagesRef(index)}>
|
|
134
|
+
<MessageBubble
|
|
135
|
+
{...props}
|
|
136
|
+
customCssClasses={cssClasses.messageBubbleCssClasses}
|
|
137
|
+
message={message}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
118
140
|
))}
|
|
119
141
|
{loading && <LoadingDots />}
|
|
120
142
|
</div>
|