@banbox/chat 1.0.10 → 1.0.12

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.10",
3
+ "version": "1.0.12",
4
4
  "description": "Banbox Chat UI components — reusable across all Banbox React/Next.js projects",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -11,30 +11,44 @@ import SinglePopup from "./SinglePopup";
11
11
  import type { ChatTheme } from "./InboxPopup";
12
12
 
13
13
  export type ChatRootProps = {
14
+ /** The unified data adapter — provides all threads, messages, and send logic. */
15
+ adapter: ChatAdapter;
16
+
17
+ /** Optional UI callbacks — toast, navigation, kebab menu. */
18
+ uiCallbacks?: ChatUICallbacks;
19
+
14
20
  /**
15
- * The unified data adapter provides all threads, messages, and send logic.
21
+ * Visual theme: "marketplace" | "admin" | { primary, primaryActive, surfaceLow }
22
+ * @example <ChatRoot adapter={adapter} theme="admin" />
16
23
  */
17
- adapter: ChatAdapter;
24
+ theme?: ChatTheme;
18
25
 
19
26
  /**
20
- * Optional UI callbacks controls toast notifications, navigation,
21
- * and the kebab (⋮) menu renderer.
27
+ * Allow-list of toolbar buttons shown in the **InboxPopup** footer.
28
+ *
29
+ * Keys: "attachment" | "emoji" | "businessCard" | "addressCard" | "translate"
30
+ * Default: ["attachment", "emoji", "translate"]
31
+ *
32
+ * @example
33
+ * // Seller inbox — no cards at all:
34
+ * inboxFooterActions={["attachment", "emoji", "translate"]}
22
35
  */
23
- uiCallbacks?: ChatUICallbacks;
36
+ inboxFooterActions?: string[];
24
37
 
25
38
  /**
26
- * Visual theme:
27
- * - "marketplace" (default) — orange primary (#ff5300)
28
- * - "admin" black primary (#1a1a1a)
29
- * - custom object — { primary, primaryActive, surfaceLow }
39
+ * Allow-list of toolbar buttons shown in the **SinglePopup** footer.
40
+ *
41
+ * Keys: "attachment" | "emoji" | "businessCard" | "addressCard" | "translate"
42
+ * Default: ["attachment", "emoji", "translate"]
30
43
  *
31
44
  * @example
32
- * <ChatRoot adapter={adapter} theme="admin" />
45
+ * // Single chat with location sharing enabled:
46
+ * singleFooterActions={["attachment", "emoji", "translate", "addressCard"]}
33
47
  */
34
- theme?: ChatTheme;
48
+ singleFooterActions?: string[];
35
49
  };
36
50
 
37
- export default function ChatRoot({ adapter, uiCallbacks, theme }: ChatRootProps) {
51
+ export default function ChatRoot({ adapter, uiCallbacks, theme, inboxFooterActions, singleFooterActions }: ChatRootProps) {
38
52
  const { isOpen, variant } = useChatUI();
39
53
 
40
54
  // Lock page scroll whenever the chat is open
@@ -45,7 +59,6 @@ export default function ChatRoot({ adapter, uiCallbacks, theme }: ChatRootProps)
45
59
  }
46
60
 
47
61
  return createPortal(
48
- // GalleryProvider is scoped to the chat only.
49
62
  <GalleryProvider>
50
63
  <AnimatePresence mode="wait">
51
64
  {isOpen && (
@@ -55,6 +68,7 @@ export default function ChatRoot({ adapter, uiCallbacks, theme }: ChatRootProps)
55
68
  adapter={adapter}
56
69
  uiCallbacks={uiCallbacks}
57
70
  theme={theme}
71
+ footerActions={inboxFooterActions}
58
72
  />
59
73
  ) : (
60
74
  <SinglePopup
@@ -62,6 +76,7 @@ export default function ChatRoot({ adapter, uiCallbacks, theme }: ChatRootProps)
62
76
  adapter={adapter}
63
77
  uiCallbacks={uiCallbacks}
64
78
  theme={theme}
79
+ footerActions={singleFooterActions}
65
80
  />
66
81
  )
67
82
  )}
@@ -34,6 +34,11 @@ export type InboxPopupProps = {
34
34
  adapter: ChatAdapter;
35
35
  uiCallbacks?: ChatUICallbacks;
36
36
  theme?: ChatTheme;
37
+ /**
38
+ * Allow-list of footer toolbar keys. Defaults to ["attachment", "emoji", "translate"].
39
+ * Pass e.g. ["attachment", "emoji", "translate", "businessCard"] to opt-in to more.
40
+ */
41
+ footerActions?: string[];
37
42
  };
38
43
 
39
44
  /* ─── Helpers ─── */
@@ -50,7 +55,7 @@ const avatarBgByInitial: Record<string, string> = {
50
55
  /* ══════════════════════════════════════════════════
51
56
  Component
52
57
  ══════════════════════════════════════════════════ */
53
- const InboxPopup: React.FC<InboxPopupProps> = ({ adapter, uiCallbacks, theme }) => {
58
+ const InboxPopup: React.FC<InboxPopupProps> = ({ adapter, uiCallbacks, theme, footerActions }) => {
54
59
  const { close, selectThread, selectedThreadId, reference } = useChatUI();
55
60
  const { isOpen: isGalleryOpen, closeGallery } = useGallery();
56
61
 
@@ -337,6 +342,7 @@ const InboxPopup: React.FC<InboxPopupProps> = ({ adapter, uiCallbacks, theme })
337
342
  replyTo={replyTo}
338
343
  clearReply={() => setReplyTo(undefined)}
339
344
  onAfterSend={() => setRev((v) => v + 1)}
345
+ enabledActions={footerActions}
340
346
  onSend={(payload) => {
341
347
  if (activeId) adapter.messages.send(activeId, payload);
342
348
  }}
@@ -55,12 +55,17 @@ export type SinglePopupProps = {
55
55
  adapter: ChatAdapter;
56
56
  uiCallbacks?: ChatUICallbacks;
57
57
  theme?: ChatTheme;
58
+ /**
59
+ * Allow-list of footer toolbar keys. Defaults to ["attachment", "emoji", "translate"].
60
+ * Pass e.g. ["attachment", "emoji", "translate", "addressCard"] to add location sharing.
61
+ */
62
+ footerActions?: string[];
58
63
  };
59
64
 
60
65
  /* ══════════════════════════════════════════════════
61
66
  Component
62
67
  ══════════════════════════════════════════════════ */
63
- const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme }) => {
68
+ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme, footerActions }) => {
64
69
  const { close, reference } = useChatUI();
65
70
  const { isOpen: isGalleryOpen, closeGallery } = useGallery();
66
71
 
@@ -258,6 +263,7 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks, theme }
258
263
  replyTo={replyTo}
259
264
  clearReply={() => setReplyTo(undefined)}
260
265
  onAfterSend={handleAfterSend}
266
+ enabledActions={footerActions}
261
267
  onSend={(payload) => {
262
268
  if (activeId) adapter.messages.send(activeId, payload);
263
269
  }}
@@ -39,9 +39,26 @@ type Props = {
39
39
  /** Called after send completes — triggers parent re-render / scroll */
40
40
  onAfterSend?: () => void;
41
41
 
42
- actions?: FooterAction[];
43
- /** Keys of actions to hide from the toolbar, e.g. ["businessCard", "addressCard"] */
44
- hiddenActionKeys?: string[];
42
+ /**
43
+ * Allow-list of toolbar action keys to show.
44
+ * If omitted, defaults to ["attachment", "emoji", "translate"].
45
+ *
46
+ * Available keys:
47
+ * "attachment" — 📎 Attach file / image
48
+ * "emoji" — 😊 Emoji picker
49
+ * "businessCard" — 👤 Share business card (opt-in)
50
+ * "addressCard" — 📍 Share address card (opt-in)
51
+ * "translate" — 🌐 Translation settings
52
+ *
53
+ * @example
54
+ * // InboxPopup: no location, no business card
55
+ * enabledActions={["attachment", "emoji", "translate"]}
56
+ *
57
+ * // SinglePopup: add location button
58
+ * enabledActions={["attachment", "emoji", "translate", "addressCard"]}
59
+ */
60
+ enabledActions?: string[];
61
+
45
62
  className?: string;
46
63
  maxRows?: number;
47
64
 
@@ -50,6 +67,13 @@ type Props = {
50
67
  clearReply?: () => void;
51
68
  };
52
69
 
70
+ /**
71
+ * Default toolbar actions when `enabledActions` is not specified.
72
+ * businessCard and addressCard are opt-in (not shown by default).
73
+ */
74
+ export const DEFAULT_FOOTER_ACTIONS: string[] = ["attachment", "emoji", "translate"];
75
+
76
+
53
77
  /* ────────────────────────── Utilities ─────────────────────────── */
54
78
 
55
79
  const fmtTime = (s: number) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
@@ -69,36 +93,22 @@ const ChatFooter: React.FC<Props> = ({
69
93
  onAfterSend,
70
94
  className,
71
95
  maxRows = 4,
72
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
- actions: _actions,
74
- hiddenActionKeys = [],
96
+ enabledActions,
75
97
  replyTo,
76
98
  clearReply,
77
99
  }) => {
78
100
  const allActions: FooterAction[] = [
79
101
  { key: "attachment", title: "Attach file", icon: <AttachIcon className="h-4 w-4" /> },
80
- { key: "emoji", title: "Add emoji", icon: <SmileIcon className="h-4 w-4" /> },
81
- {
82
- key: "businessCard",
83
- title: "Share business card",
84
- icon: <ProfileCardIcon className="h-4 w-4" />,
85
- },
86
- {
87
- key: "addressCard",
88
- title: "Share address",
89
- icon: <MapIcon2 className="h-4 w-4" />,
90
- },
91
- {
92
- key: "translate",
93
- title: "Translation settings",
94
- icon: <NewLanguageIcon className="h-4 w-4" />,
95
- },
102
+ { key: "emoji", title: "Add emoji", icon: <SmileIcon className="h-4 w-4" /> },
103
+ { key: "businessCard", title: "Share business card", icon: <ProfileCardIcon className="h-4 w-4" /> },
104
+ { key: "addressCard", title: "Share address", icon: <MapIcon2 className="h-4 w-4" /> },
105
+ { key: "translate", title: "Translation settings", icon: <NewLanguageIcon className="h-4 w-4" /> },
96
106
  ];
97
107
 
98
- // Filter hidden actions
99
- const actionData = hiddenActionKeys.length
100
- ? allActions.filter((a) => !hiddenActionKeys.includes(a.key))
101
- : allActions;
108
+ // Allow-list filter: show only explicitly requested keys.
109
+ // Falls back to DEFAULT_FOOTER_ACTIONS when enabledActions is not provided.
110
+ const allowed = enabledActions ?? DEFAULT_FOOTER_ACTIONS;
111
+ const actionData = allActions.filter((a) => allowed.includes(a.key));
102
112
 
103
113
  const textRef = useRef<HTMLTextAreaElement>(null);
104
114
  const emojiBtnRef = useRef<HTMLButtonElement | null>(null);