@clubmed/trident-ui 2.0.0 → 2.1.0-beta.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/chunks/ChatMessage.js +367 -9804
- package/chunks/ChatMessage.js.map +1 -1
- package/chunks/Datepicker.js +1 -1
- package/chunks/Tooltip.js +1 -1
- package/chunks/chunk.js +5 -5
- package/examples/chat-body-demo.d.ts +1 -0
- package/examples/chat-body-demo.js +50 -0
- package/examples/chat-body-demo.js.map +1 -0
- package/examples/chat-header-demo.d.ts +1 -0
- package/examples/chat-header-demo.js +14 -0
- package/examples/chat-header-demo.js.map +1 -0
- package/package.json +1 -2
- package/ui/ChatBody.d.ts +25 -0
- package/ui/ChatBody.js +88 -0
- package/ui/ChatBody.js.map +1 -0
- package/ui/ChatHeader.d.ts +6 -0
- package/ui/ChatHeader.js +43 -0
- package/ui/ChatHeader.js.map +1 -0
- package/ui/ChatInput.js +2 -2
- package/ui/ChatInput.js.map +1 -1
- package/ui/ChatWindow.d.ts +18 -18
- package/ui/ChatWindow.js +37 -121
- package/ui/ChatWindow.js.map +1 -1
- package/ui/contexts/TridentUIConfig.js +1 -1
- package/chunks/client.js +0 -18953
- package/chunks/client.js.map +0 -1
package/chunks/Datepicker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { n as e } from "./chunk.js";
|
|
3
3
|
import { Button as t } from "../ui/buttons/Button.js";
|
|
4
4
|
import { useDevice as n } from "../ui/contexts/devices/Device.js";
|
|
5
5
|
import { a as r, i, r as a } from "./addLeadingZeros.js";
|
package/chunks/Tooltip.js
CHANGED
package/chunks/chunk.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
//#region \0rolldown/runtime.js
|
|
2
|
-
var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescriptor, r = Object.getOwnPropertyNames, i = Object.getPrototypeOf, a = Object.prototype.hasOwnProperty, o = (e, t) => () => (
|
|
2
|
+
var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescriptor, r = Object.getOwnPropertyNames, i = Object.getPrototypeOf, a = Object.prototype.hasOwnProperty, o = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports), s = (e, n) => {
|
|
3
3
|
let r = {};
|
|
4
4
|
for (var i in e) t(r, i, {
|
|
5
5
|
get: e[i],
|
|
6
6
|
enumerable: !0
|
|
7
7
|
});
|
|
8
8
|
return n || t(r, Symbol.toStringTag, { value: "Module" }), r;
|
|
9
|
-
},
|
|
9
|
+
}, c = (e, i, o, s) => {
|
|
10
10
|
if (i && typeof i == "object" || typeof i == "function") for (var c = r(i), l = 0, u = c.length, d; l < u; l++) d = c[l], !a.call(e, d) && d !== o && t(e, d, {
|
|
11
11
|
get: ((e) => i[e]).bind(null, d),
|
|
12
12
|
enumerable: !(s = n(i, d)) || s.enumerable
|
|
13
13
|
});
|
|
14
14
|
return e;
|
|
15
|
-
},
|
|
15
|
+
}, l = (n, r, a) => (a = n == null ? {} : e(i(n)), c(r || !n || !n.__esModule ? t(a, "default", {
|
|
16
16
|
value: n,
|
|
17
17
|
enumerable: !0
|
|
18
|
-
}) : a, n)),
|
|
18
|
+
}) : a, n)), u = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
|
|
19
19
|
if (typeof require < "u") return require.apply(this, arguments);
|
|
20
20
|
throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
|
|
21
21
|
});
|
|
22
22
|
//#endregion
|
|
23
|
-
export {
|
|
23
|
+
export { l as i, s as n, u as r, o as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ChatBodyDemo(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { ChatBody as e } from "../ui/ChatBody.js";
|
|
3
|
+
import { ChatInput as t } from "../ui/ChatInput.js";
|
|
4
|
+
import { useState as n } from "react";
|
|
5
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
6
|
+
//#region lib/examples/chat-body-demo.tsx
|
|
7
|
+
function a() {
|
|
8
|
+
let [a, o] = n([{
|
|
9
|
+
id: "1",
|
|
10
|
+
sender: "assistant",
|
|
11
|
+
content: "Hi. Ask me anything about Club Med. I'm here to help you find the right information quickly."
|
|
12
|
+
}, {
|
|
13
|
+
id: "2",
|
|
14
|
+
sender: "user",
|
|
15
|
+
content: "My son is 14 years old, and I would like to find activities for him during our stay."
|
|
16
|
+
}]), [s, c] = n(""), [l, u] = n(!1);
|
|
17
|
+
function d() {
|
|
18
|
+
if (!s.trim()) return;
|
|
19
|
+
let e = {
|
|
20
|
+
id: Date.now().toString(),
|
|
21
|
+
sender: "user",
|
|
22
|
+
content: s
|
|
23
|
+
};
|
|
24
|
+
o((t) => [...t, e]), c(""), u(!0), setTimeout(() => {
|
|
25
|
+
u(!1), o((e) => [...e, {
|
|
26
|
+
id: (Date.now() + 1).toString(),
|
|
27
|
+
sender: "assistant",
|
|
28
|
+
content: "Let me look into that for you…"
|
|
29
|
+
}]);
|
|
30
|
+
}, 2e3);
|
|
31
|
+
}
|
|
32
|
+
return /* @__PURE__ */ i("div", {
|
|
33
|
+
className: "chat-border bg-white flex flex-col w-380 h-660 rounded-32 overflow-hidden",
|
|
34
|
+
children: [/* @__PURE__ */ r(e, {
|
|
35
|
+
messages: a,
|
|
36
|
+
isTyping: l
|
|
37
|
+
}), /* @__PURE__ */ r("div", {
|
|
38
|
+
className: "shrink-0 px-8 pb-8 pt-12",
|
|
39
|
+
children: /* @__PURE__ */ r(t, {
|
|
40
|
+
value: s,
|
|
41
|
+
onValueChange: c,
|
|
42
|
+
onSubmit: d
|
|
43
|
+
})
|
|
44
|
+
})]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { a as default };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=chat-body-demo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-body-demo.js","names":[],"sources":["../../lib/examples/chat-body-demo.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { ChatBody } from '@/ui/ChatBody';\nimport type { ChatMessage } from '@/ui/ChatBody';\nimport { ChatInput } from '@/ui/ChatInput';\n\nexport default function ChatBodyDemo() {\n const [messages, setMessages] = useState<ChatMessage[]>([\n {\n id: '1',\n sender: 'assistant',\n content:\n \"Hi. Ask me anything about Club Med. I'm here to help you find the right information quickly.\",\n },\n {\n id: '2',\n sender: 'user',\n content:\n 'My son is 14 years old, and I would like to find activities for him during our stay.',\n },\n ]);\n const [inputValue, setInputValue] = useState('');\n const [isTyping, setIsTyping] = useState(false);\n\n function handleSubmit() {\n if (!inputValue.trim()) return;\n const userMsg: ChatMessage = {\n id: Date.now().toString(),\n sender: 'user',\n content: inputValue,\n };\n setMessages((prev) => [...prev, userMsg]);\n setInputValue('');\n setIsTyping(true);\n setTimeout(() => {\n setIsTyping(false);\n setMessages((prev) => [\n ...prev,\n {\n id: (Date.now() + 1).toString(),\n sender: 'assistant',\n content: 'Let me look into that for you…',\n },\n ]);\n }, 2000);\n }\n\n return (\n <div className=\"chat-border bg-white flex flex-col w-380 h-660 rounded-32 overflow-hidden\">\n <ChatBody messages={messages} isTyping={isTyping} />\n <div className=\"shrink-0 px-8 pb-8 pt-12\">\n <ChatInput value={inputValue} onValueChange={setInputValue} onSubmit={handleSubmit} />\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAOA,SAAwB,IAAe;CACrC,IAAM,CAAC,GAAU,KAAe,EAAwB,CACtD;EACE,IAAI;EACJ,QAAQ;EACR,SACE;EACH,EACD;EACE,IAAI;EACJ,QAAQ;EACR,SACE;EACH,CACF,CAAC,EACI,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAU,KAAe,EAAS,GAAM;CAE/C,SAAS,IAAe;AACtB,MAAI,CAAC,EAAW,MAAM,CAAE;EACxB,IAAM,IAAuB;GAC3B,IAAI,KAAK,KAAK,CAAC,UAAU;GACzB,QAAQ;GACR,SAAS;GACV;AAID,EAHA,GAAa,MAAS,CAAC,GAAG,GAAM,EAAQ,CAAC,EACzC,EAAc,GAAG,EACjB,EAAY,GAAK,EACjB,iBAAiB;AAEf,GADA,EAAY,GAAM,EAClB,GAAa,MAAS,CACpB,GAAG,GACH;IACE,KAAK,KAAK,KAAK,GAAG,GAAG,UAAU;IAC/B,QAAQ;IACR,SAAS;IACV,CACF,CAAC;KACD,IAAK;;AAGV,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAoB;GAAoB;GAAY,CAAA,EACpD,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IAAW,OAAO;IAAY,eAAe;IAAe,UAAU;IAAgB,CAAA;GAClF,CAAA,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function ChatHeaderDemo(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { ChatHeader as e } from "../ui/ChatHeader.js";
|
|
3
|
+
import { jsx as t } from "react/jsx-runtime";
|
|
4
|
+
//#region lib/examples/chat-header-demo.tsx
|
|
5
|
+
function n() {
|
|
6
|
+
return /* @__PURE__ */ t("div", {
|
|
7
|
+
className: "w-380",
|
|
8
|
+
children: /* @__PURE__ */ t(e, { title: "Ask me a question" })
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { n as default };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=chat-header-demo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-header-demo.js","names":[],"sources":["../../lib/examples/chat-header-demo.tsx"],"sourcesContent":["'use client';\n\nimport { ChatHeader } from '@/ui/ChatHeader';\n\nexport default function ChatHeaderDemo() {\n return (\n <div className=\"w-380\">\n <ChatHeader title=\"Ask me a question\" />\n </div>\n );\n}\n"],"mappings":";;;;AAIA,SAAwB,IAAiB;AACvC,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAY,OAAM,qBAAsB,CAAA;EACpC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clubmed/trident-ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.1.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared ClubMed React UI components",
|
|
6
6
|
"keywords": [
|
|
@@ -86,7 +86,6 @@
|
|
|
86
86
|
"date-fns": ">=2.30.0",
|
|
87
87
|
"embla-carousel-react": "^8.6.0",
|
|
88
88
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
89
|
-
"leva": "^0.10.1",
|
|
90
89
|
"react-day-picker": ">=8.10.0",
|
|
91
90
|
"react-hot-toast": ">=2.4.1",
|
|
92
91
|
"react-transition-state": ">=2.3.2",
|
package/ui/ChatBody.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ChatMessageSender } from './ChatMessage';
|
|
2
|
+
import { ChatWindowCsatPromptLabels } from './ChatWindowCsatPrompt';
|
|
3
|
+
import { ChatWindowCsatThankYouLabels } from './ChatWindowCsatThankYou';
|
|
4
|
+
import { ChatWindowQuoteItem } from './ChatWindowQuote';
|
|
5
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
6
|
+
export interface ChatMessage {
|
|
7
|
+
id: string;
|
|
8
|
+
sender: ChatMessageSender;
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export interface ChatBodyProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
12
|
+
greeting?: string;
|
|
13
|
+
messages: ChatMessage[];
|
|
14
|
+
isTyping?: boolean;
|
|
15
|
+
onFeedbackRate?: (rating: number) => void;
|
|
16
|
+
csatLabels?: ChatWindowCsatPromptLabels;
|
|
17
|
+
csatRenderRatingLabel?: (rating: number) => ReactNode;
|
|
18
|
+
csatThankYouLabels?: ChatWindowCsatThankYouLabels;
|
|
19
|
+
onQuoteCtaClick?: (quote: ChatWindowQuoteItem) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface ChatHandle {
|
|
22
|
+
askForFeedback: () => void;
|
|
23
|
+
showQuotes: (quotes: ChatWindowQuoteItem[]) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare const ChatBody: import('react').ForwardRefExoticComponent<ChatBodyProps & import('react').RefAttributes<ChatHandle>>;
|
package/ui/ChatBody.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { twMerge as e } from "./helpers/twMerge.js";
|
|
2
|
+
import { t } from "../chunks/ChatMessage.js";
|
|
3
|
+
import { ChatTypingIndicator as n } from "./ChatTypingIndicator.js";
|
|
4
|
+
import { ChatWindowCsatPrompt as r } from "./ChatWindowCsatPrompt.js";
|
|
5
|
+
import { ChatWindowCsatThankYou as i } from "./ChatWindowCsatThankYou.js";
|
|
6
|
+
import { ChatWindowQuote as a } from "./ChatWindowQuote.js";
|
|
7
|
+
import { forwardRef as o, useEffect as s, useImperativeHandle as c, useRef as l, useState as u } from "react";
|
|
8
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
|
+
//#region lib/ui/ChatBody.tsx
|
|
10
|
+
function p({ messages: e, isTyping: t, csatRenderRatingLabel: n, onFeedbackRate: r, csatLabels: i }, a) {
|
|
11
|
+
let o = l(null), [d, f] = u(!1), [p, m] = u(null), [h, g] = u([]);
|
|
12
|
+
c(a, () => ({
|
|
13
|
+
askForFeedback() {
|
|
14
|
+
f(!0);
|
|
15
|
+
},
|
|
16
|
+
showQuotes(e) {
|
|
17
|
+
g((t) => [...t, e]);
|
|
18
|
+
}
|
|
19
|
+
})), s(() => {
|
|
20
|
+
let e = o.current;
|
|
21
|
+
e && e.scrollTo({
|
|
22
|
+
top: e.scrollHeight,
|
|
23
|
+
behavior: "smooth"
|
|
24
|
+
});
|
|
25
|
+
}, [
|
|
26
|
+
e,
|
|
27
|
+
t,
|
|
28
|
+
d,
|
|
29
|
+
p,
|
|
30
|
+
h
|
|
31
|
+
]);
|
|
32
|
+
function _(e) {
|
|
33
|
+
m(String(n ? n(e) : e)), r?.(e);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
logRef: o,
|
|
37
|
+
csatRatingMessage: p,
|
|
38
|
+
quoteGroups: h,
|
|
39
|
+
handleRate: _,
|
|
40
|
+
csatReady: d && i && n
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
var m = o(function(o, s) {
|
|
44
|
+
let { logRef: c, csatRatingMessage: l, quoteGroups: u, handleRate: m, csatReady: h } = p(o, s), { greeting: g, messages: _, isTyping: v = !1, csatLabels: y, csatRenderRatingLabel: b, csatThankYouLabels: x, onQuoteCtaClick: S, className: C, ...w } = o;
|
|
45
|
+
return /* @__PURE__ */ f("div", {
|
|
46
|
+
...w,
|
|
47
|
+
ref: c,
|
|
48
|
+
role: "log",
|
|
49
|
+
"aria-label": "Chat messages",
|
|
50
|
+
"aria-live": "polite",
|
|
51
|
+
className: e("chat-message-log flex flex-col flex-1 gap-20 overflow-y-auto py-20", C),
|
|
52
|
+
children: [
|
|
53
|
+
g ? /* @__PURE__ */ d("div", {
|
|
54
|
+
className: "px-20",
|
|
55
|
+
children: /* @__PURE__ */ d("p", {
|
|
56
|
+
className: "text-b3 text-black",
|
|
57
|
+
children: g
|
|
58
|
+
})
|
|
59
|
+
}) : null,
|
|
60
|
+
_.map((e) => /* @__PURE__ */ d(t, {
|
|
61
|
+
sender: e.sender,
|
|
62
|
+
children: e.content
|
|
63
|
+
}, e.id)),
|
|
64
|
+
v && /* @__PURE__ */ d("div", {
|
|
65
|
+
className: "px-12",
|
|
66
|
+
children: /* @__PURE__ */ d(n, {})
|
|
67
|
+
}),
|
|
68
|
+
h && /* @__PURE__ */ d(r, {
|
|
69
|
+
onRate: m,
|
|
70
|
+
labels: y,
|
|
71
|
+
renderRatingLabel: b
|
|
72
|
+
}),
|
|
73
|
+
l && /* @__PURE__ */ d(t, {
|
|
74
|
+
sender: "user",
|
|
75
|
+
children: l
|
|
76
|
+
}),
|
|
77
|
+
l && x && /* @__PURE__ */ d(i, { labels: x }),
|
|
78
|
+
u.map((e, t) => /* @__PURE__ */ d(a, {
|
|
79
|
+
quotes: e,
|
|
80
|
+
onCtaClick: S
|
|
81
|
+
}, t))
|
|
82
|
+
]
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//#endregion
|
|
86
|
+
export { m as ChatBody };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=ChatBody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatBody.js","names":[],"sources":["../../lib/ui/ChatBody.tsx"],"sourcesContent":["import { ChatMessage, type ChatMessageSender } from './ChatMessage';\nimport { ChatTypingIndicator } from './ChatTypingIndicator';\nimport type { ChatWindowCsatPromptLabels } from './ChatWindowCsatPrompt';\nimport { ChatWindowCsatPrompt } from './ChatWindowCsatPrompt';\nimport {\n ChatWindowCsatThankYou,\n type ChatWindowCsatThankYouLabels,\n} from './ChatWindowCsatThankYou';\nimport { ChatWindowQuote, type ChatWindowQuoteItem } from './ChatWindowQuote';\nimport {\n forwardRef,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { twMerge } from './helpers/twMerge';\n\nexport interface ChatMessage {\n id: string;\n sender: ChatMessageSender;\n content: ReactNode;\n}\n\nexport interface ChatBodyProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n greeting?: string;\n messages: ChatMessage[];\n isTyping?: boolean;\n onFeedbackRate?: (rating: number) => void;\n csatLabels?: ChatWindowCsatPromptLabels;\n csatRenderRatingLabel?: (rating: number) => ReactNode;\n csatThankYouLabels?: ChatWindowCsatThankYouLabels;\n onQuoteCtaClick?: (quote: ChatWindowQuoteItem) => void;\n}\n\nexport interface ChatHandle {\n askForFeedback: () => void;\n showQuotes: (quotes: ChatWindowQuoteItem[]) => void;\n}\n\nfunction useChat(\n { messages, isTyping, csatRenderRatingLabel, onFeedbackRate, csatLabels }: ChatBodyProps,\n ref: Ref<ChatHandle>,\n) {\n const logRef = useRef<HTMLDivElement>(null);\n const [showCsat, setShowCsat] = useState(false);\n const [csatRatingMessage, setCsatRatingMessage] = useState<string | null>(null);\n const [quoteGroups, setQuoteGroups] = useState<ChatWindowQuoteItem[][]>([]);\n\n useImperativeHandle(ref, () => ({\n askForFeedback() {\n setShowCsat(true);\n },\n showQuotes(quotes: ChatWindowQuoteItem[]) {\n setQuoteGroups((prev) => [...prev, quotes]);\n },\n }));\n\n useEffect(() => {\n const el = logRef.current;\n if (el) el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' });\n }, [messages, isTyping, showCsat, csatRatingMessage, quoteGroups]);\n\n function handleRate(rating: number) {\n const label = csatRenderRatingLabel ? String(csatRenderRatingLabel(rating)) : String(rating);\n setCsatRatingMessage(label);\n onFeedbackRate?.(rating);\n }\n\n const csatReady = showCsat && csatLabels && csatRenderRatingLabel;\n\n return { logRef, csatRatingMessage, quoteGroups, handleRate, csatReady };\n}\n\nexport const ChatBody = forwardRef<ChatHandle, ChatBodyProps>(function Chat(props, ref) {\n const { logRef, csatRatingMessage, quoteGroups, handleRate, csatReady } = useChat(props, ref);\n\n const {\n greeting,\n messages,\n isTyping = false,\n csatLabels,\n csatRenderRatingLabel,\n csatThankYouLabels,\n onQuoteCtaClick,\n className,\n ...extraProps\n } = props;\n\n return (\n <div\n {...extraProps}\n ref={logRef}\n role=\"log\"\n aria-label=\"Chat messages\"\n aria-live=\"polite\"\n className={twMerge(\n 'chat-message-log flex flex-col flex-1 gap-20 overflow-y-auto py-20',\n className,\n )}\n >\n {greeting ? (\n <div className=\"px-20\">\n <p className=\"text-b3 text-black\">{greeting}</p>\n </div>\n ) : null}\n\n {messages.map((msg) => (\n <ChatMessage key={msg.id} sender={msg.sender}>\n {msg.content}\n </ChatMessage>\n ))}\n\n {isTyping && (\n <div className=\"px-12\">\n <ChatTypingIndicator />\n </div>\n )}\n\n {csatReady && (\n <ChatWindowCsatPrompt\n onRate={handleRate}\n labels={csatLabels!}\n renderRatingLabel={csatRenderRatingLabel!}\n />\n )}\n\n {csatRatingMessage && <ChatMessage sender=\"user\">{csatRatingMessage}</ChatMessage>}\n\n {csatRatingMessage && csatThankYouLabels && (\n <ChatWindowCsatThankYou labels={csatThankYouLabels} />\n )}\n\n {quoteGroups.map((quotes, idx) => (\n <ChatWindowQuote key={idx} quotes={quotes} onCtaClick={onQuoteCtaClick} />\n ))}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;AA2CA,SAAS,EACP,EAAE,aAAU,aAAU,0BAAuB,mBAAgB,iBAC7D,GACA;CACA,IAAM,IAAS,EAAuB,KAAK,EACrC,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAmB,KAAwB,EAAwB,KAAK,EACzE,CAAC,GAAa,KAAkB,EAAkC,EAAE,CAAC;AAW3E,CATA,EAAoB,UAAY;EAC9B,iBAAiB;AACf,KAAY,GAAK;;EAEnB,WAAW,GAA+B;AACxC,MAAgB,MAAS,CAAC,GAAG,GAAM,EAAO,CAAC;;EAE9C,EAAE,EAEH,QAAgB;EACd,IAAM,IAAK,EAAO;AAClB,EAAI,KAAI,EAAG,SAAS;GAAE,KAAK,EAAG;GAAc,UAAU;GAAU,CAAC;IAChE;EAAC;EAAU;EAAU;EAAU;EAAmB;EAAY,CAAC;CAElE,SAAS,EAAW,GAAgB;AAGlC,EADA,EADsC,OAAxB,IAA+B,EAAsB,EAAO,GAAW,EAAO,CACjE,EAC3B,IAAiB,EAAO;;AAK1B,QAAO;EAAE;EAAQ;EAAmB;EAAa;EAAY,WAF3C,KAAY,KAAc;EAE4B;;AAG1E,IAAa,IAAW,EAAsC,SAAc,GAAO,GAAK;CACtF,IAAM,EAAE,WAAQ,sBAAmB,gBAAa,eAAY,iBAAc,EAAQ,GAAO,EAAI,EAEvF,EACJ,aACA,aACA,cAAW,IACX,eACA,0BACA,uBACA,oBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,OAAD;EACE,GAAI;EACJ,KAAK;EACL,MAAK;EACL,cAAW;EACX,aAAU;EACV,WAAW,EACT,sEACA,EACD;YATH;GAWG,IACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAsB;KAAa,CAAA;IAC5C,CAAA,GACJ;GAEH,EAAS,KAAK,MACb,kBAAC,GAAD;IAA0B,QAAQ,EAAI;cACnC,EAAI;IACO,EAFI,EAAI,GAER,CACd;GAED,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAuB,CAAA;IACnB,CAAA;GAGP,KACC,kBAAC,GAAD;IACE,QAAQ;IACR,QAAQ;IACR,mBAAmB;IACnB,CAAA;GAGH,KAAqB,kBAAC,GAAD;IAAa,QAAO;cAAQ;IAAgC,CAAA;GAEjF,KAAqB,KACpB,kBAAC,GAAD,EAAwB,QAAQ,GAAsB,CAAA;GAGvD,EAAY,KAAK,GAAQ,MACxB,kBAAC,GAAD;IAAmC;IAAQ,YAAY;IAAmB,EAApD,EAAoD,CAC1E;GACE;;EAER"}
|
package/ui/ChatHeader.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as e, jsxs as t } from "react/jsx-runtime";
|
|
2
|
+
import { Icon as n } from "@clubmed/trident-icons";
|
|
3
|
+
//#region lib/ui/ChatHeader.tsx
|
|
4
|
+
var r = /* @__PURE__ */ e("svg", {
|
|
5
|
+
className: "absolute top-full inset-x-[50%] -translate-x-1/2",
|
|
6
|
+
width: "118",
|
|
7
|
+
height: "27",
|
|
8
|
+
viewBox: "0 0 118 27",
|
|
9
|
+
children: /* @__PURE__ */ e("path", {
|
|
10
|
+
d: "M0 0C10.8283 0 21.1946 5.40419 28.7327 13.2062L30.2673 14.7945C45.991 31.0685 72.009 31.0685 87.7327 14.7945L89.2673 13.2062C96.8054 5.40419 107.172 0 118 0C118 0 80.6853 0 0 0Z",
|
|
11
|
+
fill: "#F6EFE7"
|
|
12
|
+
})
|
|
13
|
+
});
|
|
14
|
+
function i({ title: i = "Ask me a question", logo: a }) {
|
|
15
|
+
return /* @__PURE__ */ t("div", {
|
|
16
|
+
className: "relative flex flex-col items-center shrink-0 pt-4 px-4 h-118 overflow-hidden",
|
|
17
|
+
children: [/* @__PURE__ */ e("div", {
|
|
18
|
+
className: "absolute inset-x-4 top-4 h-87 bg-lightSand rounded-[24px]",
|
|
19
|
+
children: r
|
|
20
|
+
}), /* @__PURE__ */ t("div", {
|
|
21
|
+
className: "relative flex flex-col flex-1 items-center justify-end pb-8 w-full z-10",
|
|
22
|
+
children: [/* @__PURE__ */ e("div", {
|
|
23
|
+
className: "flex items-center justify-center px-12 py-8 w-full",
|
|
24
|
+
children: /* @__PURE__ */ e("p", {
|
|
25
|
+
className: "font-serif font-bold italic text-h5 text-center text-black line-clamp-2",
|
|
26
|
+
children: i
|
|
27
|
+
})
|
|
28
|
+
}), a ?? /* @__PURE__ */ e("button", {
|
|
29
|
+
type: "button",
|
|
30
|
+
"aria-label": "AI assistant",
|
|
31
|
+
className: "chat-border flex items-center justify-center size-48 rounded-full bg-white outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2",
|
|
32
|
+
children: /* @__PURE__ */ e(n, {
|
|
33
|
+
name: "Sparkles",
|
|
34
|
+
width: "24px"
|
|
35
|
+
})
|
|
36
|
+
})]
|
|
37
|
+
})]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { i as ChatHeader };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=ChatHeader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatHeader.js","names":[],"sources":["../../lib/ui/ChatHeader.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Icon } from '@clubmed/trident-icons';\n\nexport interface ChatHeaderProps {\n title?: string;\n logo?: ReactNode;\n}\n\nconst headerCurve = (\n <svg\n className=\"absolute top-full inset-x-[50%] -translate-x-1/2\"\n width=\"118\"\n height=\"27\"\n viewBox=\"0 0 118 27\"\n >\n <path\n d=\"M0 0C10.8283 0 21.1946 5.40419 28.7327 13.2062L30.2673 14.7945C45.991 31.0685 72.009 31.0685 87.7327 14.7945L89.2673 13.2062C96.8054 5.40419 107.172 0 118 0C118 0 80.6853 0 0 0Z\"\n fill=\"#F6EFE7\"\n />\n </svg>\n);\n\nexport function ChatHeader({ title = 'Ask me a question', logo }: ChatHeaderProps) {\n return (\n <div className=\"relative flex flex-col items-center shrink-0 pt-4 px-4 h-118 overflow-hidden\">\n <div className=\"absolute inset-x-4 top-4 h-87 bg-lightSand rounded-[24px]\">{headerCurve}</div>\n <div className=\"relative flex flex-col flex-1 items-center justify-end pb-8 w-full z-10\">\n <div className=\"flex items-center justify-center px-12 py-8 w-full\">\n <p className=\"font-serif font-bold italic text-h5 text-center text-black line-clamp-2\">\n {title}\n </p>\n </div>\n {logo ?? (\n <button\n type=\"button\"\n aria-label=\"AI assistant\"\n className=\"chat-border flex items-center justify-center size-48 rounded-full bg-white outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2\"\n >\n <Icon name=\"Sparkles\" width=\"24px\" />\n </button>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;AAQA,IAAM,IACJ,kBAAC,OAAD;CACE,WAAU;CACV,OAAM;CACN,QAAO;CACP,SAAQ;WAER,kBAAC,QAAD;EACE,GAAE;EACF,MAAK;EACL,CAAA;CACE,CAAA;AAGR,SAAgB,EAAW,EAAE,WAAQ,qBAAqB,WAAyB;AACjF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAA6D;GAAkB,CAAA,EAC9F,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV;KACC,CAAA;IACA,CAAA,EACL,KACC,kBAAC,UAAD;IACE,MAAK;IACL,cAAW;IACX,WAAU;cAEV,kBAAC,GAAD;KAAM,MAAK;KAAW,OAAM;KAAS,CAAA;IAC9B,CAAA,CAEP;KACF"}
|
package/ui/ChatInput.js
CHANGED
|
@@ -94,7 +94,7 @@ function m({ value: n, placeholder: r = "Ask a question?", onValueChange: u, onS
|
|
|
94
94
|
},
|
|
95
95
|
...g,
|
|
96
96
|
style: { paddingBottom: b ? s + l + c : s },
|
|
97
|
-
className: t("relative flex items-start overflow-hidden bg-white border border-
|
|
97
|
+
className: t("relative flex items-start overflow-hidden bg-white border border-lightGrey p-12 w-full", "transition-[padding-bottom,border-radius] duration-250 ease-out rounded-b-[24px]", b ? "rounded-32" : "rounded-[40px]", h),
|
|
98
98
|
children: [/* @__PURE__ */ a("textarea", {
|
|
99
99
|
rows: b ? 3 : 1,
|
|
100
100
|
value: n,
|
|
@@ -107,7 +107,7 @@ function m({ value: n, placeholder: r = "Ask a question?", onValueChange: u, onS
|
|
|
107
107
|
placeholder: r,
|
|
108
108
|
"aria-label": r,
|
|
109
109
|
style: { marginInlineEnd: b ? 0 : E + l },
|
|
110
|
-
className: t("scrollbar-hidden bg-transparent text-b3 py-12 outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2 resize-none w-full px-20 border border-middleGrey", "transition-[margin-inline-end,border-radius] duration-250 ease-out", b ? "text-black rounded-[
|
|
110
|
+
className: t("scrollbar-hidden bg-transparent text-b3 py-12 outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2 resize-none w-full px-20 border border-middleGrey", "transition-[margin-inline-end,border-radius] duration-250 ease-out", b ? "text-black rounded-[20px]" : "text-darkGrey placeholder:text-darkGrey rounded-[40px]")
|
|
111
111
|
}), /* @__PURE__ */ o("div", {
|
|
112
112
|
style: {
|
|
113
113
|
insetInlineStart: b ? `calc(50% - ${E / 2}px)` : `calc(100% - ${s + E}px)`,
|
package/ui/ChatInput.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatInput.js","names":[],"sources":["../../lib/ui/ChatInput.tsx"],"sourcesContent":["/// <reference types=\"@types/dom-speech-recognition\" />\n'use client';\n\nimport type { FormHTMLAttributes } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { Button } from './buttons/Button';\nimport { twMerge } from './helpers/twMerge';\n\nexport interface ChatInputProps extends FormHTMLAttributes<HTMLFormElement> {\n value: string;\n placeholder?: string;\n onValueChange: (value: string) => void;\n onSubmit: () => void;\n /** Fallback for when the browser does not support the Web Speech API */\n onAudioClick?: () => void;\n}\n\nconst FORM_PAD = 12; // matches p-12\nconst BTN_SIZE = 48; // matches size-48\nconst BTN_GAP = 8; // matches gap-8\n\n// Keep onValueChange stable inside the recognition handler without re-creating the SR instance\nfunction useCallbackRef<T extends (...args: never[]) => unknown>(fn: T) {\n const ref = useRef(fn);\n useEffect(() => {\n ref.current = fn;\n });\n return ref;\n}\n\nconst SILENCE_TIMEOUT_MS = 3_000;\n\nfunction useSpeechRecognition(onValueChange: (v: string) => void) {\n const [isSupported, setIsSupported] = useState(false);\n const [isListening, setIsListening] = useState(false);\n const recognitionRef = useRef<SpeechRecognition | null>(null);\n const silenceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const isListeningRef = useRef(false);\n const onValueChangeRef = useCallbackRef(onValueChange);\n\n function clearSilenceTimer() {\n if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);\n }\n\n function resetSilenceTimer() {\n clearSilenceTimer();\n silenceTimerRef.current = setTimeout(() => {\n isListeningRef.current = false;\n setIsListening(false);\n recognitionRef.current?.stop();\n }, SILENCE_TIMEOUT_MS);\n }\n\n useEffect(() => {\n const SR: typeof SpeechRecognition | undefined =\n (window as Window & { webkitSpeechRecognition?: typeof SpeechRecognition })\n .webkitSpeechRecognition ?? window.SpeechRecognition;\n if (!SR) return;\n\n setIsSupported(true);\n\n const recognition = new SR();\n recognition.continuous = true;\n recognition.interimResults = true;\n recognition.lang = navigator.language;\n\n recognition.onresult = (event: SpeechRecognitionEvent) => {\n const transcript = Array.from(event.results)\n .map((r: SpeechRecognitionResult) => r[0].transcript)\n .join('');\n onValueChangeRef.current(transcript);\n resetSilenceTimer();\n };\n\n recognition.onend = () => {\n clearSilenceTimer();\n // Restart automatically if the user hasn't manually stopped (browser cut us off)\n if (isListeningRef.current) {\n try {\n recognition.start();\n } catch {\n /* already starting */\n }\n } else {\n setIsListening(false);\n }\n };\n\n recognition.onerror = (e: SpeechRecognitionErrorEvent) => {\n if (e.error === 'not-allowed' || e.error === 'aborted') {\n isListeningRef.current = false;\n setIsListening(false);\n clearSilenceTimer();\n }\n };\n\n recognitionRef.current = recognition;\n return () => {\n recognition.abort();\n clearSilenceTimer();\n };\n // SR detection and instance creation only needs to run once on mount\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function toggle() {\n const recognition = recognitionRef.current;\n if (!recognition) return;\n if (isListeningRef.current) {\n isListeningRef.current = false;\n recognition.stop();\n } else {\n isListeningRef.current = true;\n setIsListening(true);\n recognition.start();\n resetSilenceTimer();\n }\n }\n\n return { isSupported, isListening, toggle };\n}\n\n/**\n * Drives the audio-level indicator by writing directly to the DOM element's\n * style — avoids triggering a React re-render on every animation frame\n * (rule: rerender-use-ref-transient-values).\n *\n * Returns a ref to attach to the indicator <span>.\n */\nfunction useAudioLevel(isListening: boolean) {\n const indicatorRef = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n const el = indicatorRef.current;\n\n if (!isListening) {\n if (el) el.style.height = '0px';\n return;\n }\n\n let animId: number;\n let stream: MediaStream | null = null;\n let ctx: AudioContext | null = null;\n\n navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then((s) => {\n stream = s;\n ctx = new AudioContext();\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n source.connect(analyser);\n const data = new Uint8Array(analyser.frequencyBinCount);\n\n function tick() {\n analyser.getByteFrequencyData(data);\n const avg = data.reduce((sum, v) => sum + v, 0) / data.length;\n // Square-root curve so quiet sounds register more visibly\n const node = indicatorRef.current;\n if (node) node.style.height = `${Math.sqrt(avg / 255) * 10}px`;\n animId = requestAnimationFrame(tick);\n }\n animId = requestAnimationFrame(tick);\n });\n\n return () => {\n cancelAnimationFrame(animId);\n stream?.getTracks().forEach((t) => t.stop());\n ctx?.close();\n };\n }, [isListening]);\n\n return indicatorRef;\n}\n\nexport function ChatInput({\n value,\n placeholder = 'Ask a question?',\n onValueChange,\n onSubmit,\n onAudioClick,\n className,\n ...props\n}: ChatInputProps) {\n const [isFocused, setIsFocused] = useState(false);\n const trimmed = value.trim();\n const isOpen = isFocused || trimmed.length > 0;\n const { isSupported, isListening, toggle } = useSpeechRecognition(onValueChange);\n const audioIndicatorRef = useAudioLevel(isListening);\n\n const showAudio = isSupported || !!onAudioClick;\n const btnW = showAudio ? BTN_SIZE + BTN_GAP + BTN_SIZE : BTN_SIZE;\n\n function handleAudioClick() {\n if (isSupported) {\n toggle();\n } else {\n onAudioClick?.();\n }\n }\n\n function handleSubmit(e: React.FormEvent) {\n e.preventDefault();\n if (trimmed) onSubmit();\n }\n\n return (\n <form\n onSubmit={handleSubmit}\n onMouseDown={(e) => {\n if ((e.target as HTMLElement).tagName !== 'TEXTAREA') e.preventDefault();\n }}\n {...props}\n style={{\n paddingBottom: isOpen ? FORM_PAD + BTN_GAP + BTN_SIZE : FORM_PAD,\n }}\n className={twMerge(\n 'relative flex items-start overflow-hidden bg-white border border-light-grey p-12 w-full',\n 'transition-[padding-bottom,border-radius] duration-250 ease-out rounded-b-[24]',\n isOpen ? 'rounded-32' : 'rounded-[40]',\n className,\n )}\n >\n <textarea\n rows={isOpen ? 3 : 1}\n value={value}\n onChange={(e) => onValueChange(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && trimmed) {\n e.preventDefault();\n onSubmit();\n }\n }}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n placeholder={placeholder}\n aria-label={placeholder}\n style={{\n marginInlineEnd: isOpen ? 0 : btnW + BTN_GAP,\n }}\n className={twMerge(\n 'scrollbar-hidden bg-transparent text-b3 py-12 outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2 resize-none w-full px-20 border border-middleGrey',\n 'transition-[margin-inline-end,border-radius] duration-250 ease-out',\n isOpen\n ? 'text-black rounded-[20]'\n : 'text-darkGrey placeholder:text-darkGrey rounded-[40]',\n )}\n />\n\n <div\n style={{\n insetInlineStart: isOpen\n ? `calc(50% - ${btnW / 2}px)`\n : `calc(100% - ${FORM_PAD + btnW}px)`,\n bottom: FORM_PAD,\n }}\n className=\"absolute flex gap-8 items-center transition-[inset-inline-start] duration-250 ease-out\"\n >\n <Button\n type=\"submit\"\n aria-label=\"Send message\"\n disabled={!trimmed}\n variant=\"circle\"\n color=\"saffron\"\n icon=\"ArrowTailRight\"\n iconWidth=\"24px\"\n />\n {showAudio && (\n <Button\n type=\"button\"\n aria-label={isListening ? 'Stop recording' : 'Use microphone'}\n aria-pressed={isListening}\n onClick={handleAudioClick}\n variant=\"circle\"\n theme={isListening ? 'solid' : 'outline'}\n color={isListening ? 'sienna' : 'black'}\n icon=\"Audio\"\n iconWidth=\"24px\"\n className={twMerge(\n 'relative',\n isListening && 'animate-pulse',\n !isListening && 'border-lightGrey',\n )}\n >\n <span\n ref={audioIndicatorRef}\n aria-hidden=\"true\"\n className=\"bg-current absolute w-5 bottom-21\"\n style={{ height: '0px' }}\n />\n </Button>\n )}\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatInput.js","names":[],"sources":["../../lib/ui/ChatInput.tsx"],"sourcesContent":["/// <reference types=\"@types/dom-speech-recognition\" />\n'use client';\n\nimport type { FormHTMLAttributes } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { Button } from './buttons/Button';\nimport { twMerge } from './helpers/twMerge';\n\nexport interface ChatInputProps extends FormHTMLAttributes<HTMLFormElement> {\n value: string;\n placeholder?: string;\n onValueChange: (value: string) => void;\n onSubmit: () => void;\n /** Fallback for when the browser does not support the Web Speech API */\n onAudioClick?: () => void;\n}\n\nconst FORM_PAD = 12; // matches p-12\nconst BTN_SIZE = 48; // matches size-48\nconst BTN_GAP = 8; // matches gap-8\n\n// Keep onValueChange stable inside the recognition handler without re-creating the SR instance\nfunction useCallbackRef<T extends (...args: never[]) => unknown>(fn: T) {\n const ref = useRef(fn);\n useEffect(() => {\n ref.current = fn;\n });\n return ref;\n}\n\nconst SILENCE_TIMEOUT_MS = 3_000;\n\nfunction useSpeechRecognition(onValueChange: (v: string) => void) {\n const [isSupported, setIsSupported] = useState(false);\n const [isListening, setIsListening] = useState(false);\n const recognitionRef = useRef<SpeechRecognition | null>(null);\n const silenceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const isListeningRef = useRef(false);\n const onValueChangeRef = useCallbackRef(onValueChange);\n\n function clearSilenceTimer() {\n if (silenceTimerRef.current) clearTimeout(silenceTimerRef.current);\n }\n\n function resetSilenceTimer() {\n clearSilenceTimer();\n silenceTimerRef.current = setTimeout(() => {\n isListeningRef.current = false;\n setIsListening(false);\n recognitionRef.current?.stop();\n }, SILENCE_TIMEOUT_MS);\n }\n\n useEffect(() => {\n const SR: typeof SpeechRecognition | undefined =\n (window as Window & { webkitSpeechRecognition?: typeof SpeechRecognition })\n .webkitSpeechRecognition ?? window.SpeechRecognition;\n if (!SR) return;\n\n setIsSupported(true);\n\n const recognition = new SR();\n recognition.continuous = true;\n recognition.interimResults = true;\n recognition.lang = navigator.language;\n\n recognition.onresult = (event: SpeechRecognitionEvent) => {\n const transcript = Array.from(event.results)\n .map((r: SpeechRecognitionResult) => r[0].transcript)\n .join('');\n onValueChangeRef.current(transcript);\n resetSilenceTimer();\n };\n\n recognition.onend = () => {\n clearSilenceTimer();\n // Restart automatically if the user hasn't manually stopped (browser cut us off)\n if (isListeningRef.current) {\n try {\n recognition.start();\n } catch {\n /* already starting */\n }\n } else {\n setIsListening(false);\n }\n };\n\n recognition.onerror = (e: SpeechRecognitionErrorEvent) => {\n if (e.error === 'not-allowed' || e.error === 'aborted') {\n isListeningRef.current = false;\n setIsListening(false);\n clearSilenceTimer();\n }\n };\n\n recognitionRef.current = recognition;\n return () => {\n recognition.abort();\n clearSilenceTimer();\n };\n // SR detection and instance creation only needs to run once on mount\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n function toggle() {\n const recognition = recognitionRef.current;\n if (!recognition) return;\n if (isListeningRef.current) {\n isListeningRef.current = false;\n recognition.stop();\n } else {\n isListeningRef.current = true;\n setIsListening(true);\n recognition.start();\n resetSilenceTimer();\n }\n }\n\n return { isSupported, isListening, toggle };\n}\n\n/**\n * Drives the audio-level indicator by writing directly to the DOM element's\n * style — avoids triggering a React re-render on every animation frame\n * (rule: rerender-use-ref-transient-values).\n *\n * Returns a ref to attach to the indicator <span>.\n */\nfunction useAudioLevel(isListening: boolean) {\n const indicatorRef = useRef<HTMLSpanElement>(null);\n\n useEffect(() => {\n const el = indicatorRef.current;\n\n if (!isListening) {\n if (el) el.style.height = '0px';\n return;\n }\n\n let animId: number;\n let stream: MediaStream | null = null;\n let ctx: AudioContext | null = null;\n\n navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then((s) => {\n stream = s;\n ctx = new AudioContext();\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 256;\n source.connect(analyser);\n const data = new Uint8Array(analyser.frequencyBinCount);\n\n function tick() {\n analyser.getByteFrequencyData(data);\n const avg = data.reduce((sum, v) => sum + v, 0) / data.length;\n // Square-root curve so quiet sounds register more visibly\n const node = indicatorRef.current;\n if (node) node.style.height = `${Math.sqrt(avg / 255) * 10}px`;\n animId = requestAnimationFrame(tick);\n }\n animId = requestAnimationFrame(tick);\n });\n\n return () => {\n cancelAnimationFrame(animId);\n stream?.getTracks().forEach((t) => t.stop());\n ctx?.close();\n };\n }, [isListening]);\n\n return indicatorRef;\n}\n\nexport function ChatInput({\n value,\n placeholder = 'Ask a question?',\n onValueChange,\n onSubmit,\n onAudioClick,\n className,\n ...props\n}: ChatInputProps) {\n const [isFocused, setIsFocused] = useState(false);\n const trimmed = value.trim();\n const isOpen = isFocused || trimmed.length > 0;\n const { isSupported, isListening, toggle } = useSpeechRecognition(onValueChange);\n const audioIndicatorRef = useAudioLevel(isListening);\n\n const showAudio = isSupported || !!onAudioClick;\n const btnW = showAudio ? BTN_SIZE + BTN_GAP + BTN_SIZE : BTN_SIZE;\n\n function handleAudioClick() {\n if (isSupported) {\n toggle();\n } else {\n onAudioClick?.();\n }\n }\n\n function handleSubmit(e: React.FormEvent) {\n e.preventDefault();\n if (trimmed) onSubmit();\n }\n\n return (\n <form\n onSubmit={handleSubmit}\n onMouseDown={(e) => {\n if ((e.target as HTMLElement).tagName !== 'TEXTAREA') e.preventDefault();\n }}\n {...props}\n style={{\n paddingBottom: isOpen ? FORM_PAD + BTN_GAP + BTN_SIZE : FORM_PAD,\n }}\n className={twMerge(\n 'relative flex items-start overflow-hidden bg-white border border-lightGrey p-12 w-full',\n 'transition-[padding-bottom,border-radius] duration-250 ease-out rounded-b-[24px]',\n isOpen ? 'rounded-32' : 'rounded-[40px]',\n className,\n )}\n >\n <textarea\n rows={isOpen ? 3 : 1}\n value={value}\n onChange={(e) => onValueChange(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && trimmed) {\n e.preventDefault();\n onSubmit();\n }\n }}\n onFocus={() => setIsFocused(true)}\n onBlur={() => setIsFocused(false)}\n placeholder={placeholder}\n aria-label={placeholder}\n style={{\n marginInlineEnd: isOpen ? 0 : btnW + BTN_GAP,\n }}\n className={twMerge(\n 'scrollbar-hidden bg-transparent text-b3 py-12 outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2 resize-none w-full px-20 border border-middleGrey',\n 'transition-[margin-inline-end,border-radius] duration-250 ease-out',\n isOpen\n ? 'text-black rounded-[20px]'\n : 'text-darkGrey placeholder:text-darkGrey rounded-[40px]',\n )}\n />\n\n <div\n style={{\n insetInlineStart: isOpen\n ? `calc(50% - ${btnW / 2}px)`\n : `calc(100% - ${FORM_PAD + btnW}px)`,\n bottom: FORM_PAD,\n }}\n className=\"absolute flex gap-8 items-center transition-[inset-inline-start] duration-250 ease-out\"\n >\n <Button\n type=\"submit\"\n aria-label=\"Send message\"\n disabled={!trimmed}\n variant=\"circle\"\n color=\"saffron\"\n icon=\"ArrowTailRight\"\n iconWidth=\"24px\"\n />\n {showAudio && (\n <Button\n type=\"button\"\n aria-label={isListening ? 'Stop recording' : 'Use microphone'}\n aria-pressed={isListening}\n onClick={handleAudioClick}\n variant=\"circle\"\n theme={isListening ? 'solid' : 'outline'}\n color={isListening ? 'sienna' : 'black'}\n icon=\"Audio\"\n iconWidth=\"24px\"\n className={twMerge(\n 'relative',\n isListening && 'animate-pulse',\n !isListening && 'border-lightGrey',\n )}\n >\n <span\n ref={audioIndicatorRef}\n aria-hidden=\"true\"\n className=\"bg-current absolute w-5 bottom-21\"\n style={{ height: '0px' }}\n />\n </Button>\n )}\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
|
package/ui/ChatWindow.d.ts
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
2
|
import { ChatMessageSender } from './ChatMessage';
|
|
3
3
|
import { ChatWindowCsatPromptLabels } from './ChatWindowCsatPrompt';
|
|
4
4
|
import { ChatWindowCsatThankYouLabels } from './ChatWindowCsatThankYou';
|
|
5
5
|
import { ChatWindowQuoteItem } from './ChatWindowQuote';
|
|
6
|
+
import { ChatHeaderProps } from './ChatHeader';
|
|
7
|
+
import { ChatBodyProps } from './ChatBody';
|
|
8
|
+
import { ChatInputProps } from './ChatInput';
|
|
6
9
|
export type { ChatWindowCsatPromptLabels, ChatWindowCsatThankYouLabels, ChatWindowQuoteItem };
|
|
7
10
|
export interface ChatWindowMessage {
|
|
8
11
|
id: string;
|
|
9
12
|
sender: ChatMessageSender;
|
|
10
|
-
content:
|
|
13
|
+
content: ReactNode;
|
|
11
14
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
onSubmit: () => void;
|
|
15
|
+
interface ChatWindowInputProps {
|
|
16
|
+
inputValue: ChatInputProps['value'];
|
|
17
|
+
inputPlaceholder?: ChatInputProps['placeholder'];
|
|
18
|
+
onInputChange: ChatInputProps['onValueChange'];
|
|
19
|
+
onSubmit: ChatInputProps['onSubmit'];
|
|
20
|
+
onAudioClick?: ChatInputProps['onAudioClick'];
|
|
21
|
+
}
|
|
22
|
+
interface ChatWindowBaseProps extends ChatHeaderProps {
|
|
21
23
|
onClose?: () => void;
|
|
22
|
-
onAudioClick?: () => void;
|
|
23
|
-
logo?: ReactNode;
|
|
24
|
-
onFeedbackRate?: (rating: number) => void;
|
|
25
|
-
csatLabels?: ChatWindowCsatPromptLabels;
|
|
26
|
-
csatRenderRatingLabel?: (rating: number) => ReactNode;
|
|
27
|
-
csatThankYouLabels?: ChatWindowCsatThankYouLabels;
|
|
28
|
-
onQuoteCtaClick?: (quote: ChatWindowQuoteItem) => void;
|
|
29
24
|
}
|
|
25
|
+
export type ChatWindowProps = ChatWindowBaseProps & (({
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
} & Partial<ChatBodyProps> & Partial<ChatWindowInputProps>) | (ChatBodyProps & ChatWindowInputProps & {
|
|
28
|
+
children?: undefined;
|
|
29
|
+
}));
|
|
30
30
|
export interface ChatWindowHandle {
|
|
31
31
|
askForFeedback: () => void;
|
|
32
32
|
showQuotes: (quotes: ChatWindowQuoteItem[]) => void;
|