@banbox/chat 1.0.7 → 1.0.9

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.
Files changed (43) hide show
  1. package/dist/index.cjs +1236 -235
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +15 -3
  4. package/dist/index.d.ts +15 -3
  5. package/dist/index.js +1160 -160
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/chat/InboxPopup.tsx +105 -42
  9. package/src/chat/SinglePopup.tsx +59 -14
  10. package/src/icons/index.tsx +55 -0
  11. package/src/index.ts +14 -12
  12. package/src/modals/ChatAddressModal.tsx +844 -0
  13. package/src/modals/{chat/ChatConfirmModal.tsx → ChatConfirmModal.tsx} +2 -2
  14. package/src/modals/ChatTranslateSettingsModal.tsx +182 -0
  15. package/src/styles/index.build.css +15 -0
  16. package/src/styles/index.css +10 -2
  17. package/src/ui/{chat/AttachmentPreviewStrip.tsx → AttachmentPreviewStrip.tsx} +2 -2
  18. package/src/ui/{chat/ChatComposerBar.tsx → ChatComposerBar.tsx} +2 -2
  19. package/src/ui/{chat/ChatFooter.tsx → ChatFooter.tsx} +102 -8
  20. package/src/ui/{chat/ChatHeader.tsx → ChatHeader.tsx} +7 -4
  21. package/src/ui/{chat/ChatIdentity.tsx → ChatIdentity.tsx} +2 -2
  22. package/src/ui/{chat/ChatInquiryBar.tsx → ChatInquiryBar.tsx} +1 -1
  23. package/src/ui/ChatKebabMenu.tsx +125 -0
  24. package/src/ui/{chat/ChatListHeader.tsx → ChatListHeader.tsx} +49 -25
  25. package/src/ui/{chat/ChatMessageItem.tsx → ChatMessageItem.tsx} +1 -1
  26. package/src/ui/ChatScroll.tsx +59 -0
  27. package/src/ui/{chat/ChatSpinner.tsx → ChatSpinner.tsx} +1 -1
  28. package/src/ui/{chat/ChatThreadItem.tsx → ChatThreadItem.tsx} +9 -16
  29. package/src/ui/{chat/MessageHoverActions.tsx → MessageHoverActions.tsx} +2 -2
  30. package/src/ui/{chat/ReplyCard.tsx → ReplyCard.tsx} +2 -2
  31. package/src/ui/{chat/TypingIndicator.tsx → TypingIndicator.tsx} +1 -1
  32. package/src/ui/{chat/drop-up → drop-up}/BusinessCardDropup.tsx +15 -3
  33. package/src/ui/{chat/drop-up → drop-up}/EmojiDropup.tsx +1 -1
  34. package/src/ui/{chat/message-items → message-items}/ChatAddressCard.tsx +4 -4
  35. package/src/ui/{chat/message-items → message-items}/ChatBubbleFiles.tsx +1 -1
  36. package/src/ui/{chat/message-items → message-items}/ChatBubbleImages.tsx +2 -2
  37. package/src/ui/{chat/message-items → message-items}/ChatBusinessCard.tsx +1 -1
  38. package/src/ui/{chat/scrollToMessage.ts → scrollToMessage.ts} +1 -1
  39. package/src/ui/{chat/types.ts → types.ts} +2 -2
  40. package/src/modals/chat/ChatTranslateSettingsModal.tsx +0 -180
  41. package/src/ui/chat/ChatScroll.tsx +0 -52
  42. /package/src/ui/{chat/message-items → message-items}/ChatBubbleAudio.tsx +0 -0
  43. /package/src/ui/{chat/message-items → message-items}/ChatBubbleText.tsx +0 -0
@@ -1,30 +1,43 @@
1
1
  "use client";
2
2
 
3
+ import clsx from "clsx";
3
4
  import type { Variants } from "framer-motion";
4
5
  import { AnimatePresence, motion } from "framer-motion";
5
6
  import React from "react";
6
- import { ChatSearchIcon, ChatXIcon, MessageIcon } from "../../icons";
7
+ import { ChatSearchIcon, ChatXIcon, MessageIcon } from "../icons";
7
8
 
8
9
  type Props = {
9
10
  className?: string;
10
11
  onClose?: () => void;
11
12
  onSearchChange?: (value: string) => void;
13
+ hideSearch?: boolean;
12
14
  };
13
15
 
14
- const ChatListHeader: React.FC<Props> = ({ className, onClose, onSearchChange }) => {
16
+ const ChatListHeader: React.FC<Props> = ({
17
+ className,
18
+ onClose,
19
+ onSearchChange,
20
+ hideSearch = false,
21
+ }) => {
15
22
  const [searching, setSearching] = React.useState(false);
16
23
  const [q, setQ] = React.useState("");
17
24
  const inputRef = React.useRef<HTMLInputElement>(null);
18
25
 
19
26
  React.useEffect(() => {
20
27
  const timer = searching
21
- ? setTimeout(() => { inputRef.current?.focus(); }, 220)
28
+ ? setTimeout(() => {
29
+ inputRef.current?.focus();
30
+ }, 220)
22
31
  : undefined;
23
- return () => { clearTimeout(timer); };
32
+ return () => {
33
+ clearTimeout(timer);
34
+ };
24
35
  }, [searching]);
25
36
 
26
37
  React.useEffect(() => {
27
- if (!searching) return;
38
+ if (!searching) {
39
+ return;
40
+ }
28
41
  const onKey = (e: KeyboardEvent) => {
29
42
  if (e.key === "Escape") {
30
43
  setSearching(false);
@@ -33,18 +46,20 @@ const ChatListHeader: React.FC<Props> = ({ className, onClose, onSearchChange })
33
46
  }
34
47
  };
35
48
  window.addEventListener("keydown", onKey);
36
- return () => { window.removeEventListener("keydown", onKey); };
49
+ return () => {
50
+ window.removeEventListener("keydown", onKey);
51
+ };
37
52
  }, [searching, onSearchChange]);
38
53
 
39
54
  const variants: Variants = {
40
55
  inFromRight: { opacity: 1, x: 0, transition: { duration: 0.18, ease: [0.16, 1, 0.3, 1] } },
41
- outToLeft: { opacity: 0, x: -24, transition: { duration: 0.16, ease: [0.4, 0, 1, 1] } },
42
- inFromLeft: { opacity: 1, x: 0, transition: { duration: 0.18, ease: [0.16, 1, 0.3, 1] } },
43
- outToRight: { opacity: 0, x: 24, transition: { duration: 0.16, ease: [0.4, 0, 1, 1] } },
56
+ outToLeft: { opacity: 0, x: -24, transition: { duration: 0.16, ease: [0.4, 0, 1, 1] } },
57
+ inFromLeft: { opacity: 1, x: 0, transition: { duration: 0.18, ease: [0.16, 1, 0.3, 1] } },
58
+ outToRight: { opacity: 0, x: 24, transition: { duration: 0.16, ease: [0.4, 0, 1, 1] } },
44
59
  };
45
60
 
46
61
  return (
47
- <div className={`h-[64px] border-b border-[#ededed]${className ? ` ${className}` : ""}`}>
62
+ <div className={clsx("h-[64px] border-b border-[#ededed]", className)}>
48
63
  <div className="flex h-full items-center px-[20px]">
49
64
  <AnimatePresence initial={false} mode="wait">
50
65
  {!searching ? (
@@ -64,13 +79,16 @@ const ChatListHeader: React.FC<Props> = ({ className, onClose, onSearchChange })
64
79
  </div>
65
80
 
66
81
  <div className="flex items-center gap-2">
67
- <button
68
- title="Search"
69
- onClick={() => setSearching(true)}
70
- className="h-9 w-9 place-items-center rounded-full hover:bg-black/5 flex items-center justify-center cursor-pointer border-none bg-transparent"
71
- >
72
- <ChatSearchIcon className="w-5 h-5" />
73
- </button>
82
+ {!hideSearch && (
83
+ <button
84
+ title="Search"
85
+ onClick={() => setSearching(true)}
86
+ className="h-9 w-9 place-items-center rounded-full hover:bg-black/5 flex items-center justify-center cursor-pointer border-none bg-transparent"
87
+ >
88
+ <ChatSearchIcon className="w-5 h-5" />
89
+ </button>
90
+ )}
91
+
74
92
  <button
75
93
  title="Close"
76
94
  onClick={onClose}
@@ -95,7 +113,7 @@ const ChatListHeader: React.FC<Props> = ({ className, onClose, onSearchChange })
95
113
  <span className="mr-2 grid h-6 w-6 shrink-0 place-items-center text-[#929292]">
96
114
  <ChatSearchIcon className="w-5 h-5" />
97
115
  </span>
98
- <span className="mr-2 h-6 w-px shrink-0 bg-[#e1e1e1]" />
116
+
99
117
  <input
100
118
  ref={inputRef}
101
119
  value={q}
@@ -108,13 +126,19 @@ const ChatListHeader: React.FC<Props> = ({ className, onClose, onSearchChange })
108
126
  />
109
127
  </div>
110
128
 
111
- <button
112
- title="Close search"
113
- onClick={() => { setSearching(false); setQ(""); onSearchChange?.(""); }}
114
- className="grid h-8 w-8 place-items-center rounded-full text-xl hover:bg-black/5 cursor-pointer border-none bg-transparent"
115
- >
116
- <ChatXIcon className="w-5 h-5" />
117
- </button>
129
+ <div>
130
+ <button
131
+ title="Close search"
132
+ onClick={() => {
133
+ setSearching(false);
134
+ setQ("");
135
+ onSearchChange?.("");
136
+ }}
137
+ className="grid h-8 w-8 place-items-center rounded-full text-xl hover:bg-black/5 cursor-pointer border-none bg-transparent"
138
+ >
139
+ <ChatXIcon className="w-5 h-5" />
140
+ </button>
141
+ </div>
118
142
  </div>
119
143
  </div>
120
144
  </motion.div>
@@ -1,4 +1,4 @@
1
- // components/ui/chat/ChatMessageItem.tsx
1
+ // ui/ChatMessageItem.tsx
2
2
  "use client";
3
3
 
4
4
  import clsx from "clsx";
@@ -0,0 +1,59 @@
1
+ // ui/ChatScroll.tsx
2
+ "use client";
3
+ import clsx from "clsx";
4
+ import React from "react";
5
+
6
+ type Props = {
7
+ top?: React.ReactNode;
8
+ children: React.ReactNode;
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ /** set true if you want short threads anchored at the bottom */
12
+ bottomAlignWhenShort?: boolean;
13
+ /** when this value changes, we auto-scroll to the bottom */
14
+ scrollKey?: string | number;
15
+ };
16
+
17
+ const ChatScroll = React.forwardRef<HTMLDivElement, Props>(function ChatScroll(
18
+ { top, children, className, bottomAlignWhenShort = false, scrollKey },
19
+ _,
20
+ ) {
21
+ const ref = React.useRef<HTMLDivElement>(null);
22
+
23
+ const scrollToBottom = React.useCallback(() => {
24
+ const el = ref.current;
25
+ if (!el) {
26
+ return;
27
+ }
28
+ el.scrollTop = el.scrollHeight;
29
+ }, []);
30
+
31
+ // On mount & when scrollKey changes — useLayoutEffect guarantees synchronous scroll before paint!
32
+ React.useLayoutEffect(() => {
33
+ scrollToBottom();
34
+ }, [scrollKey, scrollToBottom]);
35
+
36
+ return (
37
+ <div
38
+ ref={ref}
39
+ data-chat-scroll
40
+ className={clsx(
41
+ "h-full min-h-0 overflow-y-auto bg-white p-4 custom-scroll-hidden",
42
+ className,
43
+ )}
44
+ >
45
+ {/* This wrapper ensures content is at least as tall as the scroll area */}
46
+ <div
47
+ className={clsx(
48
+ "min-h-full flex flex-col",
49
+ bottomAlignWhenShort ? "justify-end" : "justify-between",
50
+ )}
51
+ >
52
+ {top}
53
+ {children}
54
+ </div>
55
+ </div>
56
+ );
57
+ });
58
+
59
+ export default ChatScroll;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { cn } from "../../utils/cn";
2
+ import { cn } from "../utils/cn";
3
3
 
4
4
  /* ===========================================================
5
5
  ChatSpinner
@@ -2,7 +2,7 @@
2
2
 
3
3
  import clsx from "clsx";
4
4
  import React from "react";
5
- import { BlueBadgeIcon } from "../../icons";
5
+ import { BlueBadgeIcon } from "../icons";
6
6
 
7
7
  export type ChatThreadStatus =
8
8
  | { kind: "seen" }
@@ -16,14 +16,13 @@ type Props = {
16
16
  verified?: boolean;
17
17
 
18
18
  title: string;
19
- preview: string; // last message snippet
20
- time: string; // "29 Jul 2025 16:51"
19
+ preview: string;
20
+ time: string;
21
21
  status: ChatThreadStatus;
22
22
 
23
- avatarText: string; // e.g. "A"
24
- avatarSrc?: string; // e.g. "A"
25
- _size?: number;
26
- avatarBg?: string; // default soft peach
23
+ avatarText: string;
24
+ avatarSrc?: string;
25
+ avatarBg?: string;
27
26
  className?: string;
28
27
  onClick?: () => void;
29
28
  };
@@ -39,28 +38,22 @@ const ChatThreadItem: React.FC<Props> = ({
39
38
  status,
40
39
  avatarText,
41
40
  avatarSrc,
42
- _size = 46,
43
41
  avatarBg = "#FFF1EC",
44
42
  className,
45
43
  onClick,
46
44
  }) => {
47
- const count = status.kind === "new" ? String(Math.max(0, status.count)).padStart(2, "0") : "";
48
-
49
45
  const statusEl = (() => {
50
46
  switch (status.kind) {
51
47
  case "seen":
52
48
  return <span className="text-[#0D5EA8]">Seen</span>;
53
-
54
49
  case "delivered":
55
50
  return <span className="text-[#B7B7B7]">Delivered</span>;
56
-
57
51
  case "new":
58
52
  return (
59
53
  <span className="text-[#E63946]">
60
- {count} New
54
+ {String(Math.max(0, status.count)).padStart(2, "0")} New
61
55
  </span>
62
56
  );
63
-
64
57
  default:
65
58
  return null;
66
59
  }
@@ -81,10 +74,10 @@ const ChatThreadItem: React.FC<Props> = ({
81
74
  )}
82
75
 
83
76
  <div className="flex items-start gap-3 border-b border-[#f8f8f8] pb-2">
84
- {/* Avatar + online */}
77
+ {/* Avatar + online dot */}
85
78
  <div className="relative mt-[2px]">
86
79
  {avatarSrc ? (
87
- <div className="grid h-9 w-9 place-items-center rounded-xs font-semibold text-2xl border border-[#f1f1f1] relative overflow-hidden">
80
+ <div className="grid h-9 w-9 place-items-center rounded-xs font-semibold text-2xl border border-[#f1f1f1]">
88
81
  <img
89
82
  src={avatarSrc}
90
83
  alt={title}
@@ -1,9 +1,9 @@
1
- // components/ui/chat/MessageHoverActions.tsx
1
+ // ui/MessageHoverActions.tsx
2
2
  "use client";
3
3
 
4
4
  import clsx from "clsx";
5
5
  import React from "react";
6
- import { MessageReplayIcon, NewLanguageIcon } from "../../icons";
6
+ import { MessageReplayIcon, NewLanguageIcon } from "../icons";
7
7
 
8
8
  type ItemButton = "replay" | "translate";
9
9
 
@@ -1,9 +1,9 @@
1
- // components/chat/ui/chat/ReplyCard.tsx
1
+ // ui/ReplyCard.tsx
2
2
  "use client";
3
3
 
4
4
  import clsx from "clsx";
5
5
  import React from "react";
6
- import { ArrowBackUpIcon, FileIcon, ChatXIcon } from "../../icons";
6
+ import { ArrowBackUpIcon, FileIcon, ChatXIcon } from "../icons";
7
7
  import { scrollToMessageById } from "./scrollToMessage";
8
8
  import type { MessageRef } from "./types";
9
9
 
@@ -6,7 +6,7 @@ import _Lottie from "lottie-react";
6
6
  const Lottie = ((_Lottie as any).default ?? _Lottie) as typeof _Lottie;
7
7
 
8
8
  import React from "react";
9
- import dots from "../../lottie/typingdotanimation2.json";
9
+ import dots from "../lottie/typingdotanimation2.json";
10
10
 
11
11
  type Props = {
12
12
  size?: number;
@@ -3,9 +3,9 @@
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
 
6
- import { BusinessInfoIcon, ChatMailIcon, ChatPhoneCallIcon, ChatXIcon } from "../../../icons";
6
+ import { BusinessInfoIcon, ChatMailIcon, ChatPhoneCallIcon, ChatXIcon } from "../../icons";
7
7
 
8
- import { cn } from "../../../utils/cn";
8
+ import { cn } from "../../utils/cn";
9
9
  import type { BusinessCard } from "../types";
10
10
 
11
11
  /* =======================
@@ -19,6 +19,8 @@ type BusinessCardDropupProps = {
19
19
  /** Position against this button (like EmojiDropup) */
20
20
  anchorRef?: React.RefObject<HTMLElement | null>;
21
21
  className?: string;
22
+ /** Called when the Edit button is clicked — opens business info modal */
23
+ onEdit?: () => void;
22
24
  };
23
25
 
24
26
  /* =======================
@@ -49,6 +51,7 @@ const BusinessCardDropup = ({
49
51
  onSend,
50
52
  anchorRef,
51
53
  className,
54
+ onEdit,
52
55
  }: BusinessCardDropupProps) => {
53
56
  const panelRef = useRef<HTMLDivElement | null>(null);
54
57
  const [pos, setPos] = useState<{ left: number; top: number } | null>(null);
@@ -233,7 +236,16 @@ const BusinessCardDropup = ({
233
236
 
234
237
  {/* Footer actions */}
235
238
  <div className="mt-3 flex justify-end gap-2">
236
-
239
+ <button
240
+ type="button"
241
+ onClick={() => {
242
+ onClose();
243
+ onEdit?.();
244
+ }}
245
+ className="h-[34px] cursor-pointer rounded-[4px] border border-[#d1d5db] bg-white px-4 text-[13px] font-medium text-black hover:bg-[#f9fafb]"
246
+ >
247
+ Edit
248
+ </button>
237
249
  <button
238
250
  type="button"
239
251
  disabled={disabled}
@@ -98,7 +98,7 @@ const EmojiDropup: React.FC<Props> = ({ open, onClose, onSelect, anchorRef, clas
98
98
  aria-label="Emoji picker"
99
99
  className={clsx("emoji-dropup", className)}
100
100
  style={{
101
- zIndex: "9999",
101
+ zIndex: 99999, // Must be above chat panel z-index (10002)
102
102
  width: WIDTH,
103
103
  left: pos?.left ?? -9999,
104
104
  top: pos?.top ?? -9999,
@@ -1,12 +1,12 @@
1
- // components/ui/chat/message-items/ChatAddressCard.tsx
1
+ // ui/message-items/ChatAddressCard.tsx
2
2
  "use client";
3
3
 
4
4
  import clsx from "clsx";
5
5
  import React from "react";
6
6
 
7
- import { ChatPhoneCallIcon, BadgeHomeIcon, MapPinIcon } from "../../../icons";
8
- import { BadgeOfficeIcon } from "../../../icons";
9
- import { cn } from "../../../utils/cn";
7
+ import { ChatPhoneCallIcon, BadgeHomeIcon, MapPinIcon } from "../../icons";
8
+ import { BadgeOfficeIcon } from "../../icons";
9
+ import { cn } from "../../utils/cn";
10
10
  import type { AddressCard } from "../types";
11
11
 
12
12
  type Props = {
@@ -1,6 +1,6 @@
1
1
  import clsx from "clsx";
2
2
  import React from "react";
3
- import { FileDownloadIcon, FileIcon } from "../../../icons";
3
+ import { FileDownloadIcon, FileIcon } from "../../icons";
4
4
 
5
5
  export type ChatFile = {
6
6
  /** Visible filename */
@@ -1,9 +1,9 @@
1
- // components/ui/chat/message-items/ChatBubbleImages.tsx
1
+ // ui/message-items/ChatBubbleImages.tsx
2
2
  "use client";
3
3
 
4
4
  import React from "react";
5
5
 
6
- import { useGallery } from "../../../contexts/GalleryContext";
6
+ import { useGallery } from "../../contexts/GalleryContext";
7
7
 
8
8
  type ImgTileProps = {
9
9
  src: string;
@@ -3,7 +3,7 @@
3
3
  import clsx from "clsx";
4
4
  import React from "react";
5
5
 
6
- import { BusinessInfoIcon, ChatMailIcon, ChatPhoneCallIcon } from "../../../icons";
6
+ import { BusinessInfoIcon, ChatMailIcon, ChatPhoneCallIcon } from "../../icons";
7
7
  import type { BusinessCard } from "../types";
8
8
 
9
9
  // const AVATAR_SIZE = 60; // uncomment if avatar section is re-enabled
@@ -1,4 +1,4 @@
1
- // components/chat/ui/chat/scrollToMessage.ts
1
+ // ui/scrollToMessage.ts
2
2
  export function scrollToMessageById(messageId: string) {
3
3
  if (!messageId) {
4
4
  return;
@@ -1,5 +1,5 @@
1
- // components/chat/ui/chat/types.ts
2
- import type { MessageAudio, MessageFile } from "../../types";
1
+ // ui/types.ts
2
+ import type { MessageAudio, MessageFile } from "../types";
3
3
 
4
4
  // Re-export global types so UI components can import from this local file
5
5
  export type { MessageAudio as ChatAudio, MessageFile as ChatFile };
@@ -1,180 +0,0 @@
1
- "use client";
2
-
3
- import clsx from "clsx";
4
- import React, { useState } from "react";
5
- import { createPortal } from "react-dom";
6
-
7
- import { ChatInfoIcon } from "../../icons";
8
- import Button from "../../ui/Button";
9
- import Select from "../../ui/Select";
10
-
11
- /* ───────── Types ───────── */
12
-
13
- export type TranslateSettings = {
14
- incomingTarget?: string;
15
- autoIncoming: boolean;
16
- enableOutgoing: boolean;
17
- outgoingFrom: string;
18
- outgoingTo: string;
19
- };
20
-
21
- type Props = {
22
- open: boolean;
23
- onClose: () => void;
24
- onSave: (settings: TranslateSettings) => void;
25
- initial?: Partial<TranslateSettings>;
26
- className?: string;
27
- /** Controls layout / placement */
28
- variant?: "single" | "group";
29
- };
30
-
31
- /* Language options */
32
- const LANG_OPTIONS = [
33
- "English",
34
- "Bangla",
35
- "Arabic",
36
- "Chinese",
37
- "French",
38
- "German",
39
- "Hindi",
40
- "Italian",
41
- "Japanese",
42
- "Korean",
43
- "Portuguese",
44
- "Russian",
45
- "Spanish",
46
- "Turkish",
47
- "Urdu",
48
- ].map((l) => ({ label: l, value: l }));
49
-
50
- const ChatTranslateSettingsModal: React.FC<Props> = ({
51
- open,
52
- onClose,
53
- onSave,
54
- initial,
55
- className,
56
- variant = "group",
57
- }) => {
58
- const [incomingTarget, setIncomingTarget] = useState(
59
- initial?.incomingTarget ?? "",
60
- );
61
-
62
- // ESC to close
63
- React.useEffect(() => {
64
- if (!open) return;
65
- const onKey = (e: KeyboardEvent) => {
66
- if (e.key === "Escape") onClose();
67
- };
68
- window.addEventListener("keydown", onKey);
69
- return () => window.removeEventListener("keydown", onKey);
70
- }, [open, onClose]);
71
-
72
- if (!open) return null;
73
-
74
- const isSingle = variant === "single";
75
-
76
- const handleSave = () => {
77
- onSave({
78
- incomingTarget,
79
- autoIncoming: true,
80
- enableOutgoing: false,
81
- outgoingFrom: "English",
82
- outgoingTo: "Bangla",
83
- });
84
- };
85
-
86
- const content = (
87
- <div
88
- className={clsx(
89
- isSingle
90
- ? "fixed inset-0 z-9999 flex"
91
- : "absolute inset-0 z-50 flex items-center justify-center",
92
- )}
93
- onClick={() => onClose()}
94
- >
95
- {/* Backdrop */}
96
- <div className={isSingle ? "fixed inset-0 bg-black/30" : "absolute inset-0 bg-black/30"} />
97
-
98
- <div
99
- role="dialog"
100
- aria-modal="true"
101
- aria-labelledby="translate-settings-title"
102
- onClick={(e) => e.stopPropagation()}
103
- className={clsx(
104
- isSingle
105
- ? "fixed bottom-6 right-6 w-[500px] max-w-[95vw]"
106
- : "relative w-[500px] max-w-[95vw]",
107
- "z-10000 overflow-visible rounded-md bg-white shadow-[0_12px_30px_rgba(0,0,0,0.18)]",
108
- className,
109
- )}
110
- >
111
- {/* Header */}
112
- <div className="flex h-[44px] w-full items-center rounded-t-md bg-[#f8f8f8] px-6 py-[7px] shadow-[0px_2px_2px_rgba(47,47,47,0.08)]">
113
- <h2
114
- id="translate-settings-title"
115
- className="w-full text-[20px] font-semibold text-black"
116
- >
117
- Translation Settings
118
- </h2>
119
- </div>
120
-
121
- {/* Body */}
122
- <div className="p-4">
123
- <div className="grid gap-2">
124
- <span className="text-[12px] font-medium text-black">
125
- Translate message into
126
- </span>
127
-
128
- <Select
129
- options={LANG_OPTIONS}
130
- value={incomingTarget}
131
- onChange={setIncomingTarget}
132
- placeholder="Select Language"
133
- size={36}
134
- />
135
- </div>
136
-
137
- <div className="mt-6 flex items-start gap-1.5 text-[#FF5300]">
138
- <ChatInfoIcon className="h-4 w-4 shrink-0" />
139
- <p className="text-xs leading-relaxed">
140
- Automatically translate incoming messages. The language you save
141
- here will be used to display all incoming messages. You can choose
142
- from Spanish, Russian, French, Arabic, Portuguese, Turkish, Bangla,
143
- and among others.
144
- </p>
145
- </div>
146
- </div>
147
-
148
- {/* Footer */}
149
- <div className="flex h-[52px] items-center justify-end rounded-b-md bg-[#f8f8f8] px-6">
150
- <div className="flex items-center gap-3">
151
- <Button
152
- onClick={onClose}
153
- variant="outlined"
154
- color="black"
155
- size="34"
156
- >
157
- Cancel
158
- </Button>
159
- <Button
160
- onClick={handleSave}
161
- variant="filled"
162
- color="black"
163
- size="34"
164
- >
165
- Update
166
- </Button>
167
- </div>
168
- </div>
169
- </div>
170
- </div>
171
- );
172
-
173
- if (isSingle) {
174
- return createPortal(content, document.body);
175
- }
176
-
177
- return content;
178
- };
179
-
180
- export default ChatTranslateSettingsModal;
@@ -1,52 +0,0 @@
1
- "use client";
2
- import React from "react";
3
-
4
- type Props = {
5
- top?: React.ReactNode;
6
- children: React.ReactNode;
7
- className?: string;
8
- style?: React.CSSProperties;
9
- bottomAlignWhenShort?: boolean;
10
- scrollKey?: string | number;
11
- };
12
-
13
- const ChatScroll: React.FC<Props> = ({
14
- top,
15
- children,
16
- className,
17
- style,
18
- bottomAlignWhenShort = false,
19
- scrollKey,
20
- }) => {
21
- const ref = React.useRef<HTMLDivElement>(null);
22
-
23
- const scrollToBottom = React.useCallback(() => {
24
- const el = ref.current;
25
- if (!el) return;
26
- el.scrollTop = el.scrollHeight;
27
- }, []);
28
-
29
- React.useEffect(() => {
30
- scrollToBottom();
31
- const id = window.setTimeout(scrollToBottom, 0);
32
- return () => window.clearTimeout(id);
33
- }, [scrollKey, scrollToBottom]);
34
-
35
- return (
36
- <div
37
- ref={ref}
38
- data-chat-scroll
39
- className={`h-full min-h-0 overflow-y-auto bg-white p-4 custom-scroll-hidden${className ? ` ${className}` : ""}`}
40
- style={style}
41
- >
42
- <div
43
- className={`min-h-full flex flex-col${bottomAlignWhenShort ? " justify-end" : " justify-start"}`}
44
- >
45
- {top}
46
- {children}
47
- </div>
48
- </div>
49
- );
50
- };
51
-
52
- export default ChatScroll;