@banbox/chat 1.0.18 → 1.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banbox/chat",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "Banbox Chat UI components — reusable across all Banbox React/Next.js projects",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -83,7 +83,7 @@ const ChatImagePreviewModal: FC<ChatImagePreviewModalProps> = ({
83
83
  <AnimatePresence>
84
84
  {isOpen && total > 0 && (
85
85
  <motion.div
86
- className="fixed inset-0 z-[10010] flex items-center justify-center"
86
+ className="fixed inset-0 z-10010 flex items-center justify-center"
87
87
  initial={{ opacity: 0, backgroundColor: "rgba(0,0,0,0)" }}
88
88
  animate={{ opacity: 1, backgroundColor: "rgba(0,0,0,0.55)" }}
89
89
  exit={{ opacity: 0, backgroundColor: "rgba(0,0,0,0)" }}
@@ -129,7 +129,7 @@ const ChatImagePreviewModal: FC<ChatImagePreviewModalProps> = ({
129
129
  type="button"
130
130
  onClick={goPrev}
131
131
  disabled={!hasPrev}
132
- className={`absolute left-0 top-1/2 -translate-y-1/2 flex h-[100px] items-center rounded-tr-[3px] rounded-br-[3px] p-[7px] backdrop-blur-[2px] shadow-[3px_0px_6px_0px_rgba(0,0,0,0.1)] transition-opacity ${hasPrev ? "cursor-pointer opacity-100" : "cursor-default opacity-30"}`}
132
+ className={`absolute left-0 top-1/2 -translate-y-1/2 flex h-[100px] items-center rounded-tr-[3px] rounded-br-[3px] p-[7px] backdrop-blur-[2px] shadow-[3px_0px_6px_0px_rgba(0,0,0,0.1)] transition-all duration-200 ${hasPrev ? "cursor-pointer opacity-100 hover:bg-white hover:shadow-[3px_0px_10px_0px_rgba(0,0,0,0.18)]" : "cursor-default opacity-30"}`}
133
133
  style={{ backgroundColor: "rgba(255,255,255,0.7)" }}
134
134
  aria-label="Previous image"
135
135
  >
@@ -149,7 +149,7 @@ const ChatImagePreviewModal: FC<ChatImagePreviewModalProps> = ({
149
149
  type="button"
150
150
  onClick={goNext}
151
151
  disabled={!hasNext}
152
- className={`absolute right-0 top-1/2 -translate-y-1/2 flex h-[100px] items-center rounded-tl-[3px] rounded-bl-[3px] p-[7px] backdrop-blur-[2px] shadow-[-3px_0px_6px_0px_rgba(0,0,0,0.1)] transition-opacity ${hasNext ? "cursor-pointer opacity-100" : "cursor-default opacity-30"}`}
152
+ className={`absolute right-0 top-1/2 -translate-y-1/2 flex h-[100px] items-center rounded-tl-[3px] rounded-bl-[3px] p-[7px] backdrop-blur-[2px] shadow-[-3px_0px_6px_0px_rgba(0,0,0,0.1)] transition-all duration-200 ${hasNext ? "cursor-pointer opacity-100 hover:bg-white hover:shadow-[-3px_0px_10px_0px_rgba(0,0,0,0.18)]" : "cursor-default opacity-30"}`}
153
153
  style={{ backgroundColor: "rgba(255,255,255,0.7)" }}
154
154
  aria-label="Next image"
155
155
  >
@@ -22,9 +22,10 @@ import type { ChatAdapter, ChatUICallbacks } from "../adapter/types";
22
22
  import type { Message, MessageRef, Reference, Thread } from "../types";
23
23
  import type { ChatTheme } from "./InboxPopup";
24
24
 
25
-
26
-
27
- function coalesceThreadId(reference: Reference | undefined, threads: Thread[]): string {
25
+ function coalesceThreadId(
26
+ reference: Reference | undefined,
27
+ threads: Thread[],
28
+ ): string {
28
29
  if (!reference?.id) return threads[0]?.id ?? "";
29
30
  const refId = reference.id;
30
31
  // Priority: exact thread.id match → orderId match → inquiryId match → first thread
@@ -51,16 +52,34 @@ function toRef(m: Message): MessageRef {
51
52
 
52
53
  /** Maps the first letter of a name to a deterministic background colour. */
53
54
  const avatarBgByInitial: Record<string, string> = {
54
- a: "#FFE4E4", b: "#E4F0FF", c: "#E4FFE9", d: "#FFF4E4", e: "#F4E4FF",
55
- f: "#FFE4F4", g: "#E4FFFF", h: "#FFFFE4", i: "#E4E4FF", j: "#FFE9E4",
56
- k: "#E4FFE4", l: "#FFE4EA", m: "#E8E4FF", n: "#E4F8FF", o: "#FFF0E4",
57
- p: "#F0FFE4", q: "#FFE4F8", r: "#E4FFEC", s: "#FFEEE4", t: "#E4EAFF",
58
- u: "#F8FFE4", v: "#FFE4EE", w: "#E4FFFA", x: "#FFF8E4", y: "#EAE4FF",
55
+ a: "#FFE4E4",
56
+ b: "#E4F0FF",
57
+ c: "#E4FFE9",
58
+ d: "#FFF4E4",
59
+ e: "#F4E4FF",
60
+ f: "#FFE4F4",
61
+ g: "#E4FFFF",
62
+ h: "#FFFFE4",
63
+ i: "#E4E4FF",
64
+ j: "#FFE9E4",
65
+ k: "#E4FFE4",
66
+ l: "#FFE4EA",
67
+ m: "#E8E4FF",
68
+ n: "#E4F8FF",
69
+ o: "#FFF0E4",
70
+ p: "#F0FFE4",
71
+ q: "#FFE4F8",
72
+ r: "#E4FFEC",
73
+ s: "#FFEEE4",
74
+ t: "#E4EAFF",
75
+ u: "#F8FFE4",
76
+ v: "#FFE4EE",
77
+ w: "#E4FFFA",
78
+ x: "#FFF8E4",
79
+ y: "#EAE4FF",
59
80
  z: "#E4FFF0",
60
81
  };
61
82
 
62
-
63
-
64
83
  export type SinglePopupProps = {
65
84
  adapter: ChatAdapter;
66
85
  uiCallbacks?: ChatUICallbacks;
@@ -75,12 +94,19 @@ export type SinglePopupProps = {
75
94
  /* ══════════════════════════════════════════════════
76
95
  Component
77
96
  ══════════════════════════════════════════════════ */
78
- const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme, footerActions }) => {
97
+ const SinglePopup: React.FC<SinglePopupProps> = ({
98
+ adapter,
99
+ uiCallbacks,
100
+ theme,
101
+ footerActions,
102
+ }) => {
79
103
  const { close, reference } = useChatUI();
80
104
  const { isOpen: isGalleryOpen, closeGallery } = useGallery();
81
105
 
82
106
  // ── Threads — subscribed so real-API updates (new msg, pin, delete) are reflected
83
- const [threads, setThreads] = React.useState<Thread[]>(() => adapter.threads.list(reference));
107
+ const [threads, setThreads] = React.useState<Thread[]>(() =>
108
+ adapter.threads.list(reference),
109
+ );
84
110
  React.useEffect(() => {
85
111
  // Refresh once on mount (covers any gap between render and subscribe)
86
112
  setThreads(adapter.threads.list(reference));
@@ -97,7 +123,9 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme,
97
123
  );
98
124
  const [activeId] = React.useState<string>(initialThreadId);
99
125
 
100
- const activeThread: Thread | undefined = threads.find((t) => t.id === activeId);
126
+ const activeThread: Thread | undefined = threads.find(
127
+ (t) => t.id === activeId,
128
+ );
101
129
  const isVerified = activeThread?.badge === true;
102
130
 
103
131
  const meta = (reference?.meta ?? {}) as {
@@ -114,13 +142,11 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme,
114
142
  const avatarSrc: string | undefined =
115
143
  meta.avatarSrc ?? activeThread?.avatarSrc;
116
144
 
117
- const initial = (
145
+ const initial =
118
146
  meta.initial ??
119
147
  activeThread?.avatarText ??
120
- (activeThread?.title ?? meta.title ?? "?").charAt(0).toUpperCase()
121
- );
122
- const avatarBg =
123
- avatarBgByInitial[initial.toLowerCase()] ?? "#E4F0FF";
148
+ (activeThread?.title ?? meta.title ?? "?").charAt(0).toUpperCase();
149
+ const avatarBg = avatarBgByInitial[initial.toLowerCase()] ?? "#E4F0FF";
124
150
 
125
151
  const title = meta.title ?? activeThread?.title ?? "Unknown";
126
152
  const online = meta.online ?? activeThread?.online ?? false;
@@ -130,7 +156,9 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme,
130
156
  activeId ? adapter.messages.list(activeId) : [],
131
157
  );
132
158
  const [scrollKey, setScrollKey] = React.useState<number>(Date.now());
133
- const [replyTo, setReplyTo] = React.useState<MessageRef | undefined>(undefined);
159
+ const [replyTo, setReplyTo] = React.useState<MessageRef | undefined>(
160
+ undefined,
161
+ );
134
162
  const [showDelete, setShowDelete] = React.useState(false);
135
163
 
136
164
  React.useEffect(() => {
@@ -149,7 +177,10 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme,
149
177
  }, [activeId, adapter]);
150
178
 
151
179
  const handleConfirmDelete = React.useCallback(() => {
152
- if (!activeId) { setShowDelete(false); return; }
180
+ if (!activeId) {
181
+ setShowDelete(false);
182
+ return;
183
+ }
153
184
  adapter.threads.delete(activeId);
154
185
  uiCallbacks?.showToast?.({
155
186
  type: "success",
@@ -253,29 +284,45 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme,
253
284
  id={m.id}
254
285
  mine={mine}
255
286
  time={m.time ?? ""}
256
- authorInitial={typeof m.author === "string" ? m.author : "U"}
287
+ authorInitial={
288
+ typeof m.author === "string" ? m.author : "U"
289
+ }
257
290
  text={m.text ?? m.content}
258
- businessCard={m.businessCard as Parameters<typeof ChatMessageItem>[0]["businessCard"]}
259
- addressCard={m.addressCard as Parameters<typeof ChatMessageItem>[0]["addressCard"]}
291
+ businessCard={
292
+ m.businessCard as Parameters<
293
+ typeof ChatMessageItem
294
+ >[0]["businessCard"]
295
+ }
296
+ addressCard={
297
+ m.addressCard as Parameters<
298
+ typeof ChatMessageItem
299
+ >[0]["addressCard"]
300
+ }
260
301
  images={m.images}
261
302
  files={m.files}
262
303
  audio={m.audio}
263
304
  replyTo={m.replyTo}
264
305
  initialSrc={m.avatarSrc}
265
306
  showStatus={isLast}
266
- status={activeThread?.status?.kind === "seen" ? "Seen" : "Delivered"}
307
+ status={
308
+ activeThread?.status?.kind === "seen"
309
+ ? "Seen"
310
+ : "Delivered"
311
+ }
267
312
  onReply={() => setReplyTo(toRef(m))}
268
313
  />
269
314
  );
270
315
  })}
271
-
272
- {/* Typing indicator */}
273
- <div className="flex items-center justify-start">
274
- <TypingIndicator />
275
- </div>
276
316
  </ChatScroll>
277
317
  </div>
278
318
 
319
+ {/* Typing indicator — sticky above footer */}
320
+ <div className="shrink-0">
321
+ <div className="flex items-center justify-start py-2 px-[16px]">
322
+ <TypingIndicator />
323
+ </div>
324
+ </div>
325
+
279
326
  {/* Footer */}
280
327
  <div className="shrink-0">
281
328
  <ChatFooter
@@ -24,7 +24,7 @@ export const FilePreviewChip: React.FC<{
24
24
  ext: string;
25
25
  onRemove: () => void;
26
26
  }> = ({ name, sizeMB, ext, onRemove }) => (
27
- <div className="mr-2 inline-flex items-center gap-3 whitespace-nowrap rounded-sm border border-[#e1e1e1] bg-white px-3 py-2 h-[65px] max-w-[185px]">
27
+ <div className="mr-2 inline-flex items-center gap-2 whitespace-nowrap rounded-sm border border-[#e1e1e1] bg-white px-3 py-2 h-[65px] max-w-[185px]">
28
28
  <div className="flex min-w-0 items-center gap-2">
29
29
  <div className="min-w-0">
30
30
  <div className="flex items-center gap-1">
@@ -38,25 +38,30 @@ export const FilePreviewChip: React.FC<{
38
38
  </div>
39
39
  </div>
40
40
  </div>
41
- <button
42
- type="button"
43
- onClick={onRemove}
44
- className="grid h-8 w-8 place-items-center rounded-full bg-white text-[#3D3D3D] shadow-[0px_2px_4px_0px_#A5A3AE4D] hover:bg-black/5"
45
- title="Remove"
46
- aria-label="Remove file"
47
- >
48
- <ChatXIcon className="h-[18px] w-[18px]" />
49
- </button>
41
+ <span>
42
+ <button
43
+ type="button"
44
+ onClick={onRemove}
45
+ className="grid h-[24px] w-[24px] cursor-pointer place-items-center rounded-full bg-white text-[#3D3D3D] shadow-[0px_2px_4px_0px_#A5A3AE4D] hover:bg-black/5"
46
+ title="Remove"
47
+ aria-label="Remove file"
48
+ >
49
+ <ChatXIcon className="h-[18px] w-[18px]" />
50
+ </button>
51
+ </span>
50
52
  </div>
51
53
  );
52
54
 
53
- const ImageThumb: React.FC<{ url: string; onRemove: () => void }> = ({ url, onRemove }) => (
55
+ const ImageThumb: React.FC<{ url: string; onRemove: () => void }> = ({
56
+ url,
57
+ onRemove,
58
+ }) => (
54
59
  <div className="relative mr-2 inline-block h-[65px] w-[65px] rounded-sm border border-[#e1e1e1] bg-[#F7F7F7]">
55
60
  <img src={url} alt="" className="h-full w-full object-cover rounded-sm" />
56
61
  <button
57
62
  type="button"
58
63
  onClick={onRemove}
59
- className="absolute left-1/2 top-1/2 z-10 grid h-6 w-6 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full bg-black/30 text-[#3D3D3D] shadow-[0px_2px_4px_0px_#A5A3AE4D]"
64
+ className="absolute left-1/2 top-1/2 z-10 grid h-6 w-6 -translate-x-1/2 -translate-y-1/2 cursor-pointer place-items-center rounded-full bg-black/30 text-[#3D3D3D] shadow-[0px_2px_4px_0px_#A5A3AE4D]"
60
65
  aria-label="Remove image"
61
66
  title="Remove image"
62
67
  >
@@ -3,7 +3,12 @@
3
3
 
4
4
  import clsx from "clsx";
5
5
  import React, { useRef, useState } from "react";
6
- import { ArrowSendAngleIcon, ArrowSendIcon, RecordMicIcon, ChatXIcon } from "../icons";
6
+ import {
7
+ ArrowSendAngleIcon,
8
+ ArrowSendIcon,
9
+ RecordMicIcon,
10
+ ChatXIcon,
11
+ } from "../icons";
7
12
 
8
13
  type Props = {
9
14
  recording: boolean;
@@ -63,18 +68,18 @@ const ChatComposerBar: React.FC<Props> = ({
63
68
  return (
64
69
  <div className="flex w-full items-stretch gap-2">
65
70
  <div
66
- className="w-full rounded-sm p-px transition-[background] duration-200"
71
+ className="w-full rounded-[6px] p-px transition-[background] duration-200"
67
72
  style={{
68
73
  background: isActiveBorder ? activeGradient : idleGradient,
69
74
  }}
70
75
  >
71
- <div className="flex min-h-[50px] w-full items-center justify-between rounded-[3px] bg-white">
76
+ <div className="flex min-h-[50px] w-full items-center justify-between rounded-[5px] bg-white">
72
77
  <div className="flex w-full items-center justify-between p-[3px]">
73
78
  {!isTyping ? (
74
79
  <button
75
80
  type="button"
76
81
  onClick={startRecording}
77
- className="grid h-[44px] w-[44px] place-items-center rounded-xs bg-[#f8f8f8] text-[#ff5301] hover:brightness-95"
82
+ className="grid h-[44px] w-[44px] cursor-pointer place-items-center rounded-xs bg-[#f8f8f8] text-[#ff5301] hover:brightness-95"
78
83
  title="Record voice"
79
84
  aria-label="Record voice"
80
85
  >
@@ -117,7 +122,9 @@ const ChatComposerBar: React.FC<Props> = ({
117
122
  />
118
123
  </div>
119
124
 
120
- {!canSendArrow && <div className="grid h-full w-px place-items-center bg-[#E7E7E7]" />}
125
+ {!canSendArrow && (
126
+ <div className="grid h-full w-px place-items-center bg-[#E7E7E7]" />
127
+ )}
121
128
 
122
129
  <div className="px-2">
123
130
  {isTyping ? (
@@ -127,7 +134,7 @@ const ChatComposerBar: React.FC<Props> = ({
127
134
  type="button"
128
135
  onClick={sendText}
129
136
  className={clsx(
130
- "ms-1 grid h-[40px] w-[40px] place-items-center rounded-full text-[#ff5301] hover:bg-[#f8f8f8]",
137
+ "ms-1 grid h-[40px] w-[40px] cursor-pointer place-items-center rounded-full text-[#ff5301] hover:bg-[#f8f8f8]",
131
138
  )}
132
139
  title={hasAttachments ? "Send attachments" : "Send"}
133
140
  aria-label="Send"
@@ -144,7 +151,7 @@ const ChatComposerBar: React.FC<Props> = ({
144
151
  disabled={!hasAttachments}
145
152
  className={clsx(
146
153
  "ms-1 grid h-[40px] w-[40px] place-items-center rounded-full hover:bg-[#f8f8f8]",
147
- hasAttachments ? "text-[#ff5301]" : "text-[#B9C3D4]",
154
+ hasAttachments ? "text-[#ff5301] cursor-pointer" : "text-[#B9C3D4] cursor-not-allowed",
148
155
  )}
149
156
  title={hasAttachments ? "Send attachments" : "Send"}
150
157
  aria-label="Send"
@@ -163,7 +170,10 @@ const ChatComposerBar: React.FC<Props> = ({
163
170
  // Recording state UI
164
171
  return (
165
172
  <div className="flex w-full items-stretch gap-2">
166
- <div className="w-full rounded-sm p-px" style={{ background: activeGradient }}>
173
+ <div
174
+ className="w-full rounded-sm p-px"
175
+ style={{ background: activeGradient }}
176
+ >
167
177
  <div className="flex min-h-[50px] w-full items-center justify-between rounded-[3px] bg-white">
168
178
  <button
169
179
  type="button"
@@ -174,9 +184,7 @@ const ChatComposerBar: React.FC<Props> = ({
174
184
  <RecordMicIcon
175
185
  className={clsx(
176
186
  "h-6 w-6",
177
- seconds % 2 === 0
178
- ? "text-[#929292]"
179
- : "text-[#ff5301]",
187
+ seconds % 2 === 0 ? "text-[#929292]" : "text-[#ff5301]",
180
188
  )}
181
189
  />
182
190
  </button>
@@ -187,7 +195,7 @@ const ChatComposerBar: React.FC<Props> = ({
187
195
  <button
188
196
  type="button"
189
197
  onClick={() => stopRecording(false)}
190
- className="grid h-8 w-8 place-items-center rounded-full text-[#3D3D3D] hover:bg-black/5"
198
+ className="grid h-8 w-8 cursor-pointer place-items-center rounded-full text-[#3D3D3D] hover:bg-black/5"
191
199
  title="Discard"
192
200
  aria-label="Discard recording"
193
201
  >
@@ -199,7 +207,7 @@ const ChatComposerBar: React.FC<Props> = ({
199
207
  <button
200
208
  type="button"
201
209
  onClick={() => stopRecording(true)}
202
- className="grid h-10 w-[40px] place-items-center rounded-full text-[#ff5301]"
210
+ className="grid h-10 w-[40px] cursor-pointer place-items-center rounded-full text-[#ff5301]"
203
211
  title="Send"
204
212
  aria-label="Send"
205
213
  >
@@ -378,7 +378,7 @@ const ChatFooter: React.FC<Props> = ({
378
378
  return (
379
379
  <span key={a.key} className="relative inline-flex">
380
380
  <Tooltip text={a.title}>
381
- <button ref={emojiBtnRef} type="button" onClick={() => setShowEmoji((v) => !v)} className="flex h-6 w-6 items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
381
+ <button ref={emojiBtnRef} type="button" onClick={() => setShowEmoji(true)} className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
382
382
  <span>{a.icon}</span>
383
383
  </button>
384
384
  </Tooltip>
@@ -388,7 +388,7 @@ const ChatFooter: React.FC<Props> = ({
388
388
  if (isTranslate) {
389
389
  return (
390
390
  <Tooltip key={a.key} text={a.title}>
391
- <button type="button" onClick={() => setShowTranslate(true)} className="flex h-6 w-6 items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
391
+ <button type="button" onClick={() => setShowTranslate(true)} className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
392
392
  <span>{a.icon}</span>
393
393
  </button>
394
394
  </Tooltip>
@@ -398,7 +398,7 @@ const ChatFooter: React.FC<Props> = ({
398
398
  return (
399
399
  <span key={a.key} className="relative inline-flex">
400
400
  <Tooltip text={a.title}>
401
- <button ref={bizBtnRef} type="button" onClick={() => setShowBiz((v) => !v)} className="flex h-6 w-6 items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
401
+ <button ref={bizBtnRef} type="button" onClick={() => setShowBiz((v) => !v)} className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
402
402
  <span>{a.icon}</span>
403
403
  </button>
404
404
  </Tooltip>
@@ -409,7 +409,7 @@ const ChatFooter: React.FC<Props> = ({
409
409
  return (
410
410
  <span key={a.key} className="relative inline-flex">
411
411
  <Tooltip text={a.title}>
412
- <button ref={addrBtnRef} type="button" onClick={() => setShowAddress(true)} className="flex h-6 w-6 items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
412
+ <button ref={addrBtnRef} type="button" onClick={() => setShowAddress(true)} className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
413
413
  <span>{a.icon}</span>
414
414
  </button>
415
415
  </Tooltip>
@@ -418,7 +418,7 @@ const ChatFooter: React.FC<Props> = ({
418
418
  }
419
419
  return (
420
420
  <Tooltip key={a.key} text={a.title}>
421
- <button type="button" onClick={isAttach ? () => fileInputRef.current?.click() : a.onClick} className="flex h-6 w-6 items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
421
+ <button type="button" onClick={isAttach ? () => fileInputRef.current?.click() : a.onClick} className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-[#0F0F0F] hover:bg-[#F4F6F8]">
422
422
  <span>{a.icon}</span>
423
423
  </button>
424
424
  </Tooltip>
@@ -33,7 +33,7 @@ const ChatInquiryBar: React.FC<Props> = ({ id, onView, className, label, buttonL
33
33
  <button
34
34
  type="button"
35
35
  onClick={onView}
36
- className="group relative inline-flex w-fit items-center justify-end text-xs font-medium text-black"
36
+ className="group relative inline-flex w-fit cursor-pointer items-center justify-end text-xs font-medium text-black"
37
37
  >
38
38
  <span className="flex items-center transition-opacity duration-300 ease-in-out group-hover:opacity-0">
39
39
  <span>{buttonLabel}</span>
@@ -63,7 +63,7 @@ const ChatThreadItem: React.FC<Props> = ({
63
63
  <button
64
64
  onClick={onClick}
65
65
  className={clsx(
66
- "relative w-full text-left px-5 py-2 hover:bg-[#f8f8f8] focus:outline-none h-[75px]",
66
+ "relative w-full cursor-pointer text-left px-5 py-2 hover:bg-[#f8f8f8] focus:outline-none h-[75px]",
67
67
  active && "bg-[#f8f8f8]",
68
68
  className,
69
69
  )}
@@ -87,7 +87,7 @@ const MessageHoverActions: React.FC<Props> = ({
87
87
  onReply?.();
88
88
  }}
89
89
  className={clsx(
90
- "inline-flex h-[22px] w-[22px] items-center justify-center rounded-sm bg-white text-[#2c2c2c] shadow-[0_1px_3px_rgba(0,0,0,0.08)] hover:bg-[#f8f8f8]",
90
+ "inline-flex h-[22px] w-[22px] cursor-pointer items-center justify-center rounded-sm bg-white text-[#2c2c2c] shadow-[0_1px_3px_rgba(0,0,0,0.08)] hover:bg-[#f8f8f8]",
91
91
  isActive("replay") && "bg-[#636363] text-white",
92
92
  )}
93
93
  >
@@ -118,7 +118,7 @@ const MessageHoverActions: React.FC<Props> = ({
118
118
  onTranslate?.();
119
119
  }}
120
120
  className={clsx(
121
- "inline-flex h-[22px] w-[22px] items-center justify-center rounded-sm bg-white text-[#2c2c2c] shadow-[0_1px_3px_rgba(0,0,0,0.08)] hover:bg-[#f8f8f8]",
121
+ "inline-flex h-[22px] w-[22px] cursor-pointer items-center justify-center rounded-sm bg-white text-[#2c2c2c] shadow-[0_1px_3px_rgba(0,0,0,0.08)] hover:bg-[#f8f8f8]",
122
122
  isActive("translate") && "bg-[#636363]! text-white",
123
123
  )}
124
124
  >
@@ -129,7 +129,7 @@ const ReplyCard: React.FC<Props> = ({ refMsg, onClose, compact, className, jumpO
129
129
  <button
130
130
  type="button"
131
131
  onClick={onClose}
132
- className="absolute right-3 top-1/2 -translate-y-1/2 grid h-8 w-8 place-items-center rounded-full bg-white text-[#3D3D3D] shadow-[0px_2px_4px_0px_#A5A3AE4D] hover:text-[#FF5300]"
132
+ className="absolute right-3 top-1/2 -translate-y-1/2 grid h-8 w-8 cursor-pointer place-items-center rounded-full bg-white text-[#3D3D3D] shadow-[0px_2px_4px_0px_#A5A3AE4D] hover:text-[#FF5300]"
133
133
  title="Remove"
134
134
  aria-label="Remove"
135
135
  >
package/src/ui/Select.tsx CHANGED
@@ -37,6 +37,7 @@ const Select: React.FC<Props> = ({ options, value, onChange, placeholder = "Sele
37
37
  className={cn(
38
38
  "flex w-full items-center justify-between rounded-[4px] border border-[#cacaca] bg-white px-3 text-[13px] text-left",
39
39
  disabled && "cursor-not-allowed opacity-50",
40
+ !disabled && "cursor-pointer",
40
41
  )}
41
42
  style={{ height: size }}
42
43
  >
@@ -54,7 +55,7 @@ const Select: React.FC<Props> = ({ options, value, onChange, placeholder = "Sele
54
55
  key={opt.value}
55
56
  type="button"
56
57
  className={cn(
57
- "flex w-full items-center px-3 py-2 text-[13px] text-left hover:bg-black/5",
58
+ "flex w-full cursor-pointer items-center px-3 py-2 text-[13px] text-left hover:bg-black/5",
58
59
  opt.value === value && "bg-black/5 font-medium",
59
60
  )}
60
61
  onClick={() => {
@@ -171,7 +171,7 @@ const BusinessCardDropup = ({
171
171
  type="button"
172
172
  onClick={onClose}
173
173
  aria-label="Close"
174
- className="grid h-8 w-8 place-items-center rounded-full hover:bg-black/5"
174
+ className="grid h-8 w-8 cursor-pointer place-items-center rounded-full hover:bg-black/5"
175
175
  >
176
176
  <ChatXIcon className="h-6 w-6" />
177
177
  </button>
@@ -74,7 +74,8 @@ const EmojiDropup: React.FC<Props> = ({ open, onClose, onSelect, anchorRef, clas
74
74
  return;
75
75
  }
76
76
  const onDoc = (e: MouseEvent) => {
77
- if (panelRef.current && !panelRef.current.contains(e.target as Node)) {
77
+ const target = e.target as Node;
78
+ if (panelRef.current && !panelRef.current.contains(target) && (!anchorRef?.current || !anchorRef.current.contains(target))) {
78
79
  onClose();
79
80
  }
80
81
  };
@@ -111,10 +112,9 @@ const EmojiDropup: React.FC<Props> = ({ open, onClose, onSelect, anchorRef, clas
111
112
  <button
112
113
  key={i}
113
114
  type="button"
114
- className="emoji-dropup__item"
115
+ className="emoji-dropup__item cursor-pointer"
115
116
  onClick={() => {
116
117
  onSelect(e);
117
- onClose();
118
118
  }}
119
119
  aria-label={`Insert ${e}`}
120
120
  >
@@ -153,7 +153,7 @@ const ChatBubbleAudio: React.FC<{ mine: boolean; audio: ChatAudio }> = ({ mine,
153
153
  aria-label={playing ? "Pause" : "Play"}
154
154
  onClick={toggle}
155
155
  className={clsx(
156
- "grid h-7 w-[34px] place-items-center rounded-md transition-colors",
156
+ "grid h-7 w-[34px] cursor-pointer place-items-center rounded-md transition-colors",
157
157
  mine ? "bg-[#F1F1F1] text-[#00486F]" : "bg-[#F1F1F1] text-[#00486F]",
158
158
  )}
159
159
  >
@@ -34,8 +34,14 @@ const FileChip: React.FC<{ file: ChatFile }> = ({ file }) => (
34
34
  <div className="flex min-w-0 items-center gap-2">
35
35
  <div className="min-w-0">
36
36
  <div className="flex items-center gap-1">
37
- <FileIcon className={clsx("h-[18px] w-[18px]", extColor(file.ext))} />{" "}
38
- <div className="truncate text-xs font-normal text-black">{file.name}</div>
37
+ <span>
38
+ <FileIcon
39
+ className={clsx("h-[18px] w-[18px]", extColor(file.ext))}
40
+ />
41
+ </span>{" "}
42
+ <div className="truncate text-xs font-normal text-black">
43
+ {file.name}
44
+ </div>
39
45
  </div>
40
46
  <div className="flex items-center gap-2 text-[10px] text-[#636363] mt-2">
41
47
  <span>{file.sizeMB.toFixed(1)} MB</span>