@better-zap/react 0.2.1 → 0.2.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.
Files changed (50) hide show
  1. package/dist/bubble.cjs +93 -0
  2. package/dist/bubble.d.cts +61 -0
  3. package/dist/bubble.d.mts +61 -0
  4. package/dist/bubble.mjs +89 -0
  5. package/dist/composer.cjs +220 -0
  6. package/dist/composer.d.cts +96 -0
  7. package/dist/composer.d.mts +96 -0
  8. package/dist/composer.mjs +211 -0
  9. package/dist/conversation-list-BlhJhDsc.d.cts +100 -0
  10. package/dist/conversation-list-BwQ4oK2J.d.mts +100 -0
  11. package/dist/conversation-list-C0EmReS-.mjs +293 -0
  12. package/dist/conversation-list-CVWdddJh.cjs +311 -0
  13. package/dist/conversation-list.cjs +6 -0
  14. package/dist/conversation-list.d.cts +2 -0
  15. package/dist/conversation-list.d.mts +2 -0
  16. package/dist/conversation-list.mjs +5 -0
  17. package/dist/index.cjs +49 -0
  18. package/dist/index.d.cts +19 -0
  19. package/dist/index.d.mts +19 -0
  20. package/dist/index.mjs +11 -0
  21. package/dist/message-bubble.cjs +116 -0
  22. package/dist/message-bubble.d.cts +38 -0
  23. package/dist/message-bubble.d.mts +38 -0
  24. package/dist/message-bubble.mjs +114 -0
  25. package/dist/message-input-CkaL6fb4.mjs +173 -0
  26. package/dist/message-input-DcEzHQ9t.cjs +191 -0
  27. package/dist/message-input.cjs +6 -0
  28. package/dist/message-input.d.cts +51 -0
  29. package/dist/message-input.d.mts +51 -0
  30. package/dist/message-input.mjs +5 -0
  31. package/dist/message-view.cjs +273 -0
  32. package/dist/message-view.d.cts +109 -0
  33. package/dist/message-view.d.mts +109 -0
  34. package/dist/message-view.mjs +266 -0
  35. package/dist/message.cjs +50 -0
  36. package/dist/message.d.cts +40 -0
  37. package/dist/message.d.mts +40 -0
  38. package/dist/message.mjs +43 -0
  39. package/dist/tailwind.css +33 -0
  40. package/dist/utils-Bp9IahGG.cjs +91 -0
  41. package/dist/utils.cjs +5 -0
  42. package/dist/utils.d.cts +20 -0
  43. package/dist/utils.d.mts +20 -0
  44. package/dist/utils.mjs +45 -0
  45. package/dist/whatsapp-dashboard.cjs +64 -0
  46. package/dist/whatsapp-dashboard.d.cts +33 -0
  47. package/dist/whatsapp-dashboard.d.mts +33 -0
  48. package/dist/whatsapp-dashboard.mjs +60 -0
  49. package/dist/wpp-bg.webp +0 -0
  50. package/package.json +1 -1
@@ -0,0 +1,293 @@
1
+ import { cn } from "./utils.mjs";
2
+ import { useOptionalWhatsappDashboard } from "./whatsapp-dashboard.mjs";
3
+ import React, { useCallback, useMemo, useRef, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { HugeiconsIcon } from "@hugeicons/react";
6
+ import { Message01Icon, Search01Icon, UserIcon } from "@hugeicons/core-free-icons";
7
+ import { LegendList } from "@legendapp/list/react";
8
+ //#region src/conversation-search.tsx
9
+ /**
10
+ * Memoized: with a stable onChange, sibling state changes (filter chips,
11
+ * selection) don't re-render the search box.
12
+ */
13
+ const ConversationSearch = React.memo(function ConversationSearch({ value, onChange, className, placeholder, "aria-label": ariaLabel }) {
14
+ const effectivePlaceholder = placeholder ?? "Buscar conversa";
15
+ const effectiveAriaLabel = ariaLabel ?? effectivePlaceholder;
16
+ return /* @__PURE__ */ jsx("div", {
17
+ className: cn("shrink-0 px-3 py-2", className),
18
+ children: /* @__PURE__ */ jsxs("div", {
19
+ className: "flex items-center bg-[#f0f2f5] rounded-full px-3 h-[35px] gap-3 border border-transparent transition-colors focus-within:bg-white focus-within:border-[#e9edef]",
20
+ children: [/* @__PURE__ */ jsx(HugeiconsIcon, {
21
+ icon: Search01Icon,
22
+ size: 18,
23
+ className: "text-[#54656f] shrink-0"
24
+ }), /* @__PURE__ */ jsx("input", {
25
+ type: "text",
26
+ placeholder: effectivePlaceholder,
27
+ "aria-label": effectiveAriaLabel,
28
+ value,
29
+ onChange: (e) => onChange(e.target.value),
30
+ className: "flex-1 border-none bg-transparent text-[15px] text-[#111b21] focus:outline-none h-full placeholder:text-[#667781]"
31
+ })]
32
+ })
33
+ });
34
+ });
35
+ //#endregion
36
+ //#region src/conversation-filter-chips.tsx
37
+ /**
38
+ * Memoized: with stable onValueChange and labels, typing in the search box
39
+ * doesn't re-render the chips.
40
+ */
41
+ const ConversationFilterChips = React.memo(function ConversationFilterChips({ value, onValueChange, unreadCount = 0, className, labels }) {
42
+ const chips = [{
43
+ label: labels?.all ?? "Tudo",
44
+ value: "all"
45
+ }, {
46
+ label: labels?.unread ?? "Não lidas",
47
+ value: "unread"
48
+ }];
49
+ return /* @__PURE__ */ jsx("div", {
50
+ className: cn("flex items-center gap-1.5 px-3 pb-2 pt-1", className),
51
+ children: chips.map((chip) => {
52
+ const isActive = chip.value === value;
53
+ const showCount = chip.value === "unread" && unreadCount > 0;
54
+ return /* @__PURE__ */ jsxs("button", {
55
+ type: "button",
56
+ onClick: () => onValueChange(chip.value),
57
+ "aria-pressed": isActive,
58
+ className: cn("inline-flex h-8 items-center rounded-full px-3 text-[14px] transition-colors", isActive ? "bg-[#e7fce3] text-[#008069]" : "bg-[#f0f2f5] text-[#54656f] hover:bg-[#e9edef]"),
59
+ children: [/* @__PURE__ */ jsx("span", { children: chip.label }), showCount ? /* @__PURE__ */ jsx("span", {
60
+ className: "ml-1",
61
+ children: unreadCount
62
+ }) : null]
63
+ }, chip.value);
64
+ })
65
+ });
66
+ });
67
+ //#endregion
68
+ //#region src/conversation-list.tsx
69
+ const DEFAULT_LABELS = {
70
+ searchPlaceholder: "Buscar conversa",
71
+ searchLabel: "Buscar conversa",
72
+ filterAll: "Tudo",
73
+ filterUnread: "Não lidas",
74
+ loading: "Carregando...",
75
+ error: "Erro ao carregar conversas",
76
+ empty: "Nenhuma conversa encontrada",
77
+ outgoingPrefix: "Você: ",
78
+ noPreview: "Sem mensagem",
79
+ yesterday: "Ontem"
80
+ };
81
+ /**
82
+ * Memoized default row. Every prop is a primitive or a stable reference, so
83
+ * selecting a conversation re-renders exactly two rows (the newly selected
84
+ * and the previously selected one), not the whole list.
85
+ */
86
+ const ConversationListRow = React.memo(function ConversationListRow({ conversation, isSelected, onSelectConversation, avatar, outgoingPrefix, noPreviewLabel, formatTime }) {
87
+ return /* @__PURE__ */ jsx(ConversationItem, {
88
+ conversation,
89
+ isSelected,
90
+ onClick: useCallback(() => {
91
+ onSelectConversation(conversation.id);
92
+ }, [onSelectConversation, conversation.id]),
93
+ avatar,
94
+ outgoingPrefix,
95
+ noPreviewLabel,
96
+ formatTime
97
+ });
98
+ });
99
+ function ConversationList({ conversations, isLoading = false, isError, selectedConversationId, onSelect, search: searchProp, defaultSearch = "", onSearchChange, filter: filterProp, defaultFilter = "all", onFilterChange, renderItem, renderAvatar, formatTime: formatTimeProp, labels: labelsProp, className, ...props }) {
100
+ const dashboard = useOptionalWhatsappDashboard();
101
+ const isMobile = dashboard?.isMobile ?? false;
102
+ const mobileView = dashboard?.mobileView ?? "list";
103
+ const isSearchControlled = searchProp !== void 0;
104
+ const [internalSearch, setInternalSearch] = useState(defaultSearch);
105
+ const search = isSearchControlled ? searchProp : internalSearch;
106
+ const isFilterControlled = filterProp !== void 0;
107
+ const [internalFilter, setInternalFilter] = useState(defaultFilter);
108
+ const filter = isFilterControlled ? filterProp : internalFilter;
109
+ const labels = {
110
+ ...DEFAULT_LABELS,
111
+ ...labelsProp,
112
+ searchLabel: labelsProp?.searchLabel ?? labelsProp?.searchPlaceholder ?? DEFAULT_LABELS.searchLabel
113
+ };
114
+ const isSearchControlledRef = useRef(isSearchControlled);
115
+ isSearchControlledRef.current = isSearchControlled;
116
+ const isFilterControlledRef = useRef(isFilterControlled);
117
+ isFilterControlledRef.current = isFilterControlled;
118
+ const onSearchChangeRef = useRef(onSearchChange);
119
+ onSearchChangeRef.current = onSearchChange;
120
+ const onFilterChangeRef = useRef(onFilterChange);
121
+ onFilterChangeRef.current = onFilterChange;
122
+ const onSelectRef = useRef(onSelect);
123
+ onSelectRef.current = onSelect;
124
+ const dashboardRef = useRef(dashboard);
125
+ dashboardRef.current = dashboard;
126
+ const formatTimePropRef = useRef(formatTimeProp);
127
+ formatTimePropRef.current = formatTimeProp;
128
+ const yesterdayLabelRef = useRef(labels.yesterday);
129
+ yesterdayLabelRef.current = labels.yesterday;
130
+ const handleSearchChange = useCallback((value) => {
131
+ if (!isSearchControlledRef.current) setInternalSearch(value);
132
+ onSearchChangeRef.current?.(value);
133
+ }, []);
134
+ const handleFilterChange = useCallback((value) => {
135
+ if (!isFilterControlledRef.current) setInternalFilter(value);
136
+ onFilterChangeRef.current?.(value);
137
+ }, []);
138
+ const handleSelect = useCallback((id) => {
139
+ onSelectRef.current?.(id);
140
+ dashboardRef.current?.setMobileView("chat");
141
+ }, []);
142
+ const effectiveFormatTime = useCallback((isoDate) => formatTimePropRef.current ? formatTimePropRef.current(isoDate) : formatTimeDefault(isoDate, yesterdayLabelRef.current), []);
143
+ const chipLabels = useMemo(() => ({
144
+ all: labels.filterAll,
145
+ unread: labels.filterUnread
146
+ }), [labels.filterAll, labels.filterUnread]);
147
+ const normalizedSearch = search.trim().toLowerCase();
148
+ const effectiveFilter = filter;
149
+ const unreadConversationsCount = useMemo(() => conversations.filter((c) => c.unreadCount > 0).length, [conversations]);
150
+ const filtered = useMemo(() => {
151
+ return conversations.filter((conversation) => {
152
+ const matchesSearch = normalizedSearch.length === 0 || conversation.phone.toLowerCase().includes(normalizedSearch) || conversation.contactName?.toLowerCase().includes(normalizedSearch);
153
+ const matchesFilter = effectiveFilter === "all" || conversation.unreadCount > 0;
154
+ return matchesSearch && matchesFilter;
155
+ });
156
+ }, [
157
+ conversations,
158
+ normalizedSearch,
159
+ effectiveFilter
160
+ ]);
161
+ const isVisible = !isMobile || mobileView === "list";
162
+ return /* @__PURE__ */ jsxs("div", {
163
+ className: cn("flex flex-col h-full bg-white border-r border-[#e9edef]", isMobile ? "w-full" : "min-w-[320px] max-w-105", className),
164
+ ...props,
165
+ style: isVisible ? props.style : { display: "none" },
166
+ children: [
167
+ /* @__PURE__ */ jsx(ConversationSearch, {
168
+ value: search,
169
+ onChange: handleSearchChange,
170
+ placeholder: labels.searchPlaceholder,
171
+ "aria-label": labels.searchLabel
172
+ }),
173
+ /* @__PURE__ */ jsx(ConversationFilterChips, {
174
+ value: filter,
175
+ onValueChange: handleFilterChange,
176
+ unreadCount: unreadConversationsCount,
177
+ labels: chipLabels
178
+ }),
179
+ /* @__PURE__ */ jsx("div", {
180
+ className: "min-h-0 flex-1",
181
+ children: isLoading ? /* @__PURE__ */ jsx("div", {
182
+ className: "flex items-center justify-center h-full text-sm text-[#667781]",
183
+ children: labels.loading
184
+ }) : isError ? /* @__PURE__ */ jsx("div", {
185
+ className: "flex items-center justify-center h-full text-sm text-red-500",
186
+ children: labels.error
187
+ }) : filtered.length === 0 ? /* @__PURE__ */ jsxs("div", {
188
+ className: "flex flex-col items-center justify-center h-full gap-2 text-[#667781]",
189
+ children: [/* @__PURE__ */ jsx(HugeiconsIcon, {
190
+ icon: Message01Icon,
191
+ size: 32
192
+ }), /* @__PURE__ */ jsx("p", {
193
+ className: "text-sm",
194
+ children: labels.empty
195
+ })]
196
+ }) : /* @__PURE__ */ jsx(LegendList, {
197
+ className: "chat-scrollbar",
198
+ data: filtered,
199
+ estimatedItemSize: 72,
200
+ extraData: selectedConversationId,
201
+ getFixedItemSize: () => 72,
202
+ keyExtractor: (conversation) => conversation.id,
203
+ recycleItems: true,
204
+ renderItem: ({ item: conversation }) => {
205
+ const isSelected = selectedConversationId === conversation.id;
206
+ if (renderItem) return renderItem(conversation, {
207
+ isSelected,
208
+ select: () => handleSelect(conversation.id)
209
+ });
210
+ return /* @__PURE__ */ jsx(ConversationListRow, {
211
+ conversation,
212
+ isSelected,
213
+ onSelectConversation: handleSelect,
214
+ avatar: renderAvatar?.(conversation),
215
+ outgoingPrefix: labels.outgoingPrefix,
216
+ noPreviewLabel: labels.noPreview,
217
+ formatTime: effectiveFormatTime
218
+ });
219
+ },
220
+ style: {
221
+ height: "100%",
222
+ overflowX: "hidden"
223
+ }
224
+ })
225
+ })
226
+ ]
227
+ });
228
+ }
229
+ function ConversationItem({ conversation, isSelected = false, avatar, outgoingPrefix = "Você: ", noPreviewLabel = "Sem mensagem", formatTime: formatTimeProp, className, ...props }) {
230
+ const timeLabel = formatTimeProp ? formatTimeProp(conversation.lastMessageAt) : formatTimeDefault(conversation.lastMessageAt, "Ontem");
231
+ const hasUnread = conversation.unreadCount > 0;
232
+ return /* @__PURE__ */ jsxs("button", {
233
+ ...props,
234
+ type: "button",
235
+ "data-selected": isSelected,
236
+ "aria-current": isSelected ? "true" : void 0,
237
+ className: cn("group flex items-center w-full h-[72px] px-3 gap-3 transition-colors cursor-pointer text-left relative overflow-hidden hover:bg-[#f5f6f6] data-[selected=true]:bg-[#f0f2f5]", className),
238
+ children: [avatar ?? /* @__PURE__ */ jsx("div", {
239
+ className: "w-[49px] h-[49px] rounded-full bg-[#dfe5e7] flex items-center justify-center shrink-0",
240
+ children: /* @__PURE__ */ jsx(HugeiconsIcon, {
241
+ icon: UserIcon,
242
+ size: 28,
243
+ className: "text-white"
244
+ })
245
+ }), /* @__PURE__ */ jsxs("div", {
246
+ className: "flex-1 min-w-0 border-b border-[#e9edef]/70 h-full flex flex-col justify-center pr-1 group-last:border-none group-data-[selected=true]:border-transparent",
247
+ children: [/* @__PURE__ */ jsxs("div", {
248
+ className: "flex justify-between items-baseline mb-0.5",
249
+ children: [/* @__PURE__ */ jsx("span", {
250
+ className: "text-[17px] font-normal text-[#111b21] truncate",
251
+ children: conversation.contactName || formatPhone(conversation.phone)
252
+ }), /* @__PURE__ */ jsx("span", {
253
+ className: cn("text-xs shrink-0", hasUnread ? "text-[#1daa61]" : "text-[#667781]"),
254
+ children: timeLabel
255
+ })]
256
+ }), /* @__PURE__ */ jsxs("div", {
257
+ className: "flex justify-between items-center gap-2",
258
+ children: [/* @__PURE__ */ jsxs("p", {
259
+ className: "text-[14px] text-[#667781] truncate",
260
+ children: [conversation.lastDirection === "incoming" ? "" : outgoingPrefix, conversation.lastMessagePreview || noPreviewLabel]
261
+ }), hasUnread && /* @__PURE__ */ jsx("span", {
262
+ className: "bg-[#25d366] text-white text-[11px] font-semibold rounded-full min-w-[20px] h-[20px] flex items-center justify-center px-1.5 shrink-0",
263
+ children: conversation.unreadCount
264
+ })]
265
+ })]
266
+ })]
267
+ });
268
+ }
269
+ function formatPhone(phone) {
270
+ if (phone.length === 13 && phone.startsWith("55")) return `(${phone.slice(2, 4)}) ${phone.slice(4, 9)}-${phone.slice(9)}`;
271
+ return phone;
272
+ }
273
+ function formatTimeDefault(dateStr, yesterdayLabel) {
274
+ try {
275
+ const date = new Date(dateStr);
276
+ const now = /* @__PURE__ */ new Date();
277
+ if (date.toDateString() === now.toDateString()) return date.toLocaleTimeString("pt-BR", {
278
+ hour: "2-digit",
279
+ minute: "2-digit"
280
+ });
281
+ const yesterday = new Date(now);
282
+ yesterday.setDate(yesterday.getDate() - 1);
283
+ if (date.toDateString() === yesterday.toDateString()) return yesterdayLabel;
284
+ return date.toLocaleDateString("pt-BR", {
285
+ day: "2-digit",
286
+ month: "2-digit"
287
+ });
288
+ } catch {
289
+ return "";
290
+ }
291
+ }
292
+ //#endregion
293
+ export { ConversationList as n, ConversationFilterChips as r, ConversationItem as t };
@@ -0,0 +1,311 @@
1
+ const require_utils = require("./utils-Bp9IahGG.cjs");
2
+ const require_whatsapp_dashboard = require("./whatsapp-dashboard.cjs");
3
+ let react = require("react");
4
+ react = require_utils.__toESM(react);
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ let _hugeicons_react = require("@hugeicons/react");
7
+ let _hugeicons_core_free_icons = require("@hugeicons/core-free-icons");
8
+ let _legendapp_list_react = require("@legendapp/list/react");
9
+ //#region src/conversation-search.tsx
10
+ /**
11
+ * Memoized: with a stable onChange, sibling state changes (filter chips,
12
+ * selection) don't re-render the search box.
13
+ */
14
+ const ConversationSearch = react.default.memo(function ConversationSearch({ value, onChange, className, placeholder, "aria-label": ariaLabel }) {
15
+ const effectivePlaceholder = placeholder ?? "Buscar conversa";
16
+ const effectiveAriaLabel = ariaLabel ?? effectivePlaceholder;
17
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18
+ className: require_utils.cn("shrink-0 px-3 py-2", className),
19
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
20
+ className: "flex items-center bg-[#f0f2f5] rounded-full px-3 h-[35px] gap-3 border border-transparent transition-colors focus-within:bg-white focus-within:border-[#e9edef]",
21
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
22
+ icon: _hugeicons_core_free_icons.Search01Icon,
23
+ size: 18,
24
+ className: "text-[#54656f] shrink-0"
25
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
26
+ type: "text",
27
+ placeholder: effectivePlaceholder,
28
+ "aria-label": effectiveAriaLabel,
29
+ value,
30
+ onChange: (e) => onChange(e.target.value),
31
+ className: "flex-1 border-none bg-transparent text-[15px] text-[#111b21] focus:outline-none h-full placeholder:text-[#667781]"
32
+ })]
33
+ })
34
+ });
35
+ });
36
+ //#endregion
37
+ //#region src/conversation-filter-chips.tsx
38
+ /**
39
+ * Memoized: with stable onValueChange and labels, typing in the search box
40
+ * doesn't re-render the chips.
41
+ */
42
+ const ConversationFilterChips = react.default.memo(function ConversationFilterChips({ value, onValueChange, unreadCount = 0, className, labels }) {
43
+ const chips = [{
44
+ label: labels?.all ?? "Tudo",
45
+ value: "all"
46
+ }, {
47
+ label: labels?.unread ?? "Não lidas",
48
+ value: "unread"
49
+ }];
50
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
51
+ className: require_utils.cn("flex items-center gap-1.5 px-3 pb-2 pt-1", className),
52
+ children: chips.map((chip) => {
53
+ const isActive = chip.value === value;
54
+ const showCount = chip.value === "unread" && unreadCount > 0;
55
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
56
+ type: "button",
57
+ onClick: () => onValueChange(chip.value),
58
+ "aria-pressed": isActive,
59
+ className: require_utils.cn("inline-flex h-8 items-center rounded-full px-3 text-[14px] transition-colors", isActive ? "bg-[#e7fce3] text-[#008069]" : "bg-[#f0f2f5] text-[#54656f] hover:bg-[#e9edef]"),
60
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: chip.label }), showCount ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
61
+ className: "ml-1",
62
+ children: unreadCount
63
+ }) : null]
64
+ }, chip.value);
65
+ })
66
+ });
67
+ });
68
+ //#endregion
69
+ //#region src/conversation-list.tsx
70
+ const DEFAULT_LABELS = {
71
+ searchPlaceholder: "Buscar conversa",
72
+ searchLabel: "Buscar conversa",
73
+ filterAll: "Tudo",
74
+ filterUnread: "Não lidas",
75
+ loading: "Carregando...",
76
+ error: "Erro ao carregar conversas",
77
+ empty: "Nenhuma conversa encontrada",
78
+ outgoingPrefix: "Você: ",
79
+ noPreview: "Sem mensagem",
80
+ yesterday: "Ontem"
81
+ };
82
+ /**
83
+ * Memoized default row. Every prop is a primitive or a stable reference, so
84
+ * selecting a conversation re-renders exactly two rows (the newly selected
85
+ * and the previously selected one), not the whole list.
86
+ */
87
+ const ConversationListRow = react.default.memo(function ConversationListRow({ conversation, isSelected, onSelectConversation, avatar, outgoingPrefix, noPreviewLabel, formatTime }) {
88
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationItem, {
89
+ conversation,
90
+ isSelected,
91
+ onClick: (0, react.useCallback)(() => {
92
+ onSelectConversation(conversation.id);
93
+ }, [onSelectConversation, conversation.id]),
94
+ avatar,
95
+ outgoingPrefix,
96
+ noPreviewLabel,
97
+ formatTime
98
+ });
99
+ });
100
+ function ConversationList({ conversations, isLoading = false, isError, selectedConversationId, onSelect, search: searchProp, defaultSearch = "", onSearchChange, filter: filterProp, defaultFilter = "all", onFilterChange, renderItem, renderAvatar, formatTime: formatTimeProp, labels: labelsProp, className, ...props }) {
101
+ const dashboard = require_whatsapp_dashboard.useOptionalWhatsappDashboard();
102
+ const isMobile = dashboard?.isMobile ?? false;
103
+ const mobileView = dashboard?.mobileView ?? "list";
104
+ const isSearchControlled = searchProp !== void 0;
105
+ const [internalSearch, setInternalSearch] = (0, react.useState)(defaultSearch);
106
+ const search = isSearchControlled ? searchProp : internalSearch;
107
+ const isFilterControlled = filterProp !== void 0;
108
+ const [internalFilter, setInternalFilter] = (0, react.useState)(defaultFilter);
109
+ const filter = isFilterControlled ? filterProp : internalFilter;
110
+ const labels = {
111
+ ...DEFAULT_LABELS,
112
+ ...labelsProp,
113
+ searchLabel: labelsProp?.searchLabel ?? labelsProp?.searchPlaceholder ?? DEFAULT_LABELS.searchLabel
114
+ };
115
+ const isSearchControlledRef = (0, react.useRef)(isSearchControlled);
116
+ isSearchControlledRef.current = isSearchControlled;
117
+ const isFilterControlledRef = (0, react.useRef)(isFilterControlled);
118
+ isFilterControlledRef.current = isFilterControlled;
119
+ const onSearchChangeRef = (0, react.useRef)(onSearchChange);
120
+ onSearchChangeRef.current = onSearchChange;
121
+ const onFilterChangeRef = (0, react.useRef)(onFilterChange);
122
+ onFilterChangeRef.current = onFilterChange;
123
+ const onSelectRef = (0, react.useRef)(onSelect);
124
+ onSelectRef.current = onSelect;
125
+ const dashboardRef = (0, react.useRef)(dashboard);
126
+ dashboardRef.current = dashboard;
127
+ const formatTimePropRef = (0, react.useRef)(formatTimeProp);
128
+ formatTimePropRef.current = formatTimeProp;
129
+ const yesterdayLabelRef = (0, react.useRef)(labels.yesterday);
130
+ yesterdayLabelRef.current = labels.yesterday;
131
+ const handleSearchChange = (0, react.useCallback)((value) => {
132
+ if (!isSearchControlledRef.current) setInternalSearch(value);
133
+ onSearchChangeRef.current?.(value);
134
+ }, []);
135
+ const handleFilterChange = (0, react.useCallback)((value) => {
136
+ if (!isFilterControlledRef.current) setInternalFilter(value);
137
+ onFilterChangeRef.current?.(value);
138
+ }, []);
139
+ const handleSelect = (0, react.useCallback)((id) => {
140
+ onSelectRef.current?.(id);
141
+ dashboardRef.current?.setMobileView("chat");
142
+ }, []);
143
+ const effectiveFormatTime = (0, react.useCallback)((isoDate) => formatTimePropRef.current ? formatTimePropRef.current(isoDate) : formatTimeDefault(isoDate, yesterdayLabelRef.current), []);
144
+ const chipLabels = (0, react.useMemo)(() => ({
145
+ all: labels.filterAll,
146
+ unread: labels.filterUnread
147
+ }), [labels.filterAll, labels.filterUnread]);
148
+ const normalizedSearch = search.trim().toLowerCase();
149
+ const effectiveFilter = filter;
150
+ const unreadConversationsCount = (0, react.useMemo)(() => conversations.filter((c) => c.unreadCount > 0).length, [conversations]);
151
+ const filtered = (0, react.useMemo)(() => {
152
+ return conversations.filter((conversation) => {
153
+ const matchesSearch = normalizedSearch.length === 0 || conversation.phone.toLowerCase().includes(normalizedSearch) || conversation.contactName?.toLowerCase().includes(normalizedSearch);
154
+ const matchesFilter = effectiveFilter === "all" || conversation.unreadCount > 0;
155
+ return matchesSearch && matchesFilter;
156
+ });
157
+ }, [
158
+ conversations,
159
+ normalizedSearch,
160
+ effectiveFilter
161
+ ]);
162
+ const isVisible = !isMobile || mobileView === "list";
163
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
164
+ className: require_utils.cn("flex flex-col h-full bg-white border-r border-[#e9edef]", isMobile ? "w-full" : "min-w-[320px] max-w-105", className),
165
+ ...props,
166
+ style: isVisible ? props.style : { display: "none" },
167
+ children: [
168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationSearch, {
169
+ value: search,
170
+ onChange: handleSearchChange,
171
+ placeholder: labels.searchPlaceholder,
172
+ "aria-label": labels.searchLabel
173
+ }),
174
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationFilterChips, {
175
+ value: filter,
176
+ onValueChange: handleFilterChange,
177
+ unreadCount: unreadConversationsCount,
178
+ labels: chipLabels
179
+ }),
180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
181
+ className: "min-h-0 flex-1",
182
+ children: isLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
183
+ className: "flex items-center justify-center h-full text-sm text-[#667781]",
184
+ children: labels.loading
185
+ }) : isError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
186
+ className: "flex items-center justify-center h-full text-sm text-red-500",
187
+ children: labels.error
188
+ }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
189
+ className: "flex flex-col items-center justify-center h-full gap-2 text-[#667781]",
190
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
191
+ icon: _hugeicons_core_free_icons.Message01Icon,
192
+ size: 32
193
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
194
+ className: "text-sm",
195
+ children: labels.empty
196
+ })]
197
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_legendapp_list_react.LegendList, {
198
+ className: "chat-scrollbar",
199
+ data: filtered,
200
+ estimatedItemSize: 72,
201
+ extraData: selectedConversationId,
202
+ getFixedItemSize: () => 72,
203
+ keyExtractor: (conversation) => conversation.id,
204
+ recycleItems: true,
205
+ renderItem: ({ item: conversation }) => {
206
+ const isSelected = selectedConversationId === conversation.id;
207
+ if (renderItem) return renderItem(conversation, {
208
+ isSelected,
209
+ select: () => handleSelect(conversation.id)
210
+ });
211
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationListRow, {
212
+ conversation,
213
+ isSelected,
214
+ onSelectConversation: handleSelect,
215
+ avatar: renderAvatar?.(conversation),
216
+ outgoingPrefix: labels.outgoingPrefix,
217
+ noPreviewLabel: labels.noPreview,
218
+ formatTime: effectiveFormatTime
219
+ });
220
+ },
221
+ style: {
222
+ height: "100%",
223
+ overflowX: "hidden"
224
+ }
225
+ })
226
+ })
227
+ ]
228
+ });
229
+ }
230
+ function ConversationItem({ conversation, isSelected = false, avatar, outgoingPrefix = "Você: ", noPreviewLabel = "Sem mensagem", formatTime: formatTimeProp, className, ...props }) {
231
+ const timeLabel = formatTimeProp ? formatTimeProp(conversation.lastMessageAt) : formatTimeDefault(conversation.lastMessageAt, "Ontem");
232
+ const hasUnread = conversation.unreadCount > 0;
233
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
234
+ ...props,
235
+ type: "button",
236
+ "data-selected": isSelected,
237
+ "aria-current": isSelected ? "true" : void 0,
238
+ className: require_utils.cn("group flex items-center w-full h-[72px] px-3 gap-3 transition-colors cursor-pointer text-left relative overflow-hidden hover:bg-[#f5f6f6] data-[selected=true]:bg-[#f0f2f5]", className),
239
+ children: [avatar ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
240
+ className: "w-[49px] h-[49px] rounded-full bg-[#dfe5e7] flex items-center justify-center shrink-0",
241
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
242
+ icon: _hugeicons_core_free_icons.UserIcon,
243
+ size: 28,
244
+ className: "text-white"
245
+ })
246
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
247
+ className: "flex-1 min-w-0 border-b border-[#e9edef]/70 h-full flex flex-col justify-center pr-1 group-last:border-none group-data-[selected=true]:border-transparent",
248
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
249
+ className: "flex justify-between items-baseline mb-0.5",
250
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
251
+ className: "text-[17px] font-normal text-[#111b21] truncate",
252
+ children: conversation.contactName || formatPhone(conversation.phone)
253
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
254
+ className: require_utils.cn("text-xs shrink-0", hasUnread ? "text-[#1daa61]" : "text-[#667781]"),
255
+ children: timeLabel
256
+ })]
257
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
258
+ className: "flex justify-between items-center gap-2",
259
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
260
+ className: "text-[14px] text-[#667781] truncate",
261
+ children: [conversation.lastDirection === "incoming" ? "" : outgoingPrefix, conversation.lastMessagePreview || noPreviewLabel]
262
+ }), hasUnread && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
263
+ className: "bg-[#25d366] text-white text-[11px] font-semibold rounded-full min-w-[20px] h-[20px] flex items-center justify-center px-1.5 shrink-0",
264
+ children: conversation.unreadCount
265
+ })]
266
+ })]
267
+ })]
268
+ });
269
+ }
270
+ function formatPhone(phone) {
271
+ if (phone.length === 13 && phone.startsWith("55")) return `(${phone.slice(2, 4)}) ${phone.slice(4, 9)}-${phone.slice(9)}`;
272
+ return phone;
273
+ }
274
+ function formatTimeDefault(dateStr, yesterdayLabel) {
275
+ try {
276
+ const date = new Date(dateStr);
277
+ const now = /* @__PURE__ */ new Date();
278
+ if (date.toDateString() === now.toDateString()) return date.toLocaleTimeString("pt-BR", {
279
+ hour: "2-digit",
280
+ minute: "2-digit"
281
+ });
282
+ const yesterday = new Date(now);
283
+ yesterday.setDate(yesterday.getDate() - 1);
284
+ if (date.toDateString() === yesterday.toDateString()) return yesterdayLabel;
285
+ return date.toLocaleDateString("pt-BR", {
286
+ day: "2-digit",
287
+ month: "2-digit"
288
+ });
289
+ } catch {
290
+ return "";
291
+ }
292
+ }
293
+ //#endregion
294
+ Object.defineProperty(exports, "ConversationFilterChips", {
295
+ enumerable: true,
296
+ get: function() {
297
+ return ConversationFilterChips;
298
+ }
299
+ });
300
+ Object.defineProperty(exports, "ConversationItem", {
301
+ enumerable: true,
302
+ get: function() {
303
+ return ConversationItem;
304
+ }
305
+ });
306
+ Object.defineProperty(exports, "ConversationList", {
307
+ enumerable: true,
308
+ get: function() {
309
+ return ConversationList;
310
+ }
311
+ });
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ require("./whatsapp-dashboard.cjs");
4
+ const require_conversation_list = require("./conversation-list-CVWdddJh.cjs");
5
+ exports.ConversationItem = require_conversation_list.ConversationItem;
6
+ exports.ConversationList = require_conversation_list.ConversationList;
@@ -0,0 +1,2 @@
1
+ import { a as ConversationListProps, i as ConversationListLabels, n as ConversationItemProps, r as ConversationList, t as ConversationItem } from "./conversation-list-BlhJhDsc.cjs";
2
+ export { ConversationItem, ConversationItemProps, ConversationList, ConversationListLabels, ConversationListProps };
@@ -0,0 +1,2 @@
1
+ import { a as ConversationListProps, i as ConversationListLabels, n as ConversationItemProps, r as ConversationList, t as ConversationItem } from "./conversation-list-BwQ4oK2J.mjs";
2
+ export { ConversationItem, ConversationItemProps, ConversationList, ConversationListLabels, ConversationListProps };
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ import "./utils.mjs";
3
+ import "./whatsapp-dashboard.mjs";
4
+ import { n as ConversationList, t as ConversationItem } from "./conversation-list-C0EmReS-.mjs";
5
+ export { ConversationItem, ConversationList };
package/dist/index.cjs ADDED
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_utils = require("./utils-Bp9IahGG.cjs");
4
+ const require_whatsapp_dashboard = require("./whatsapp-dashboard.cjs");
5
+ const require_bubble = require("./bubble.cjs");
6
+ const require_message = require("./message.cjs");
7
+ const require_message_bubble = require("./message-bubble.cjs");
8
+ const require_message_view = require("./message-view.cjs");
9
+ const require_conversation_list = require("./conversation-list-CVWdddJh.cjs");
10
+ const require_composer = require("./composer.cjs");
11
+ const require_message_input = require("./message-input-DcEzHQ9t.cjs");
12
+ exports.Bubble = require_bubble.Bubble;
13
+ exports.BubbleContent = require_bubble.BubbleContent;
14
+ exports.BubbleGroup = require_bubble.BubbleGroup;
15
+ exports.BubbleReactions = require_bubble.BubbleReactions;
16
+ exports.Composer = require_composer.Composer;
17
+ exports.ComposerButton = require_composer.ComposerButton;
18
+ exports.ComposerError = require_composer.ComposerError;
19
+ exports.ComposerSend = require_composer.ComposerSend;
20
+ exports.ComposerTextarea = require_composer.ComposerTextarea;
21
+ exports.ConversationFilterChips = require_conversation_list.ConversationFilterChips;
22
+ exports.ConversationItem = require_conversation_list.ConversationItem;
23
+ exports.ConversationList = require_conversation_list.ConversationList;
24
+ exports.DateDivider = require_message_view.DateDivider;
25
+ exports.FormattedMessage = require_message_bubble.FormattedMessage;
26
+ exports.FreeformWindowClosedError = require_message_input.FreeformWindowClosedError;
27
+ exports.Message = require_message.Message;
28
+ exports.MessageAvatar = require_message.MessageAvatar;
29
+ exports.MessageBubble = require_message_bubble.MessageBubble;
30
+ exports.MessageContent = require_message.MessageContent;
31
+ exports.MessageFooter = require_message.MessageFooter;
32
+ exports.MessageGroup = require_message.MessageGroup;
33
+ exports.MessageHeader = require_message.MessageHeader;
34
+ exports.MessageInput = require_message_input.MessageInput;
35
+ exports.MessageList = require_message_view.MessageList;
36
+ exports.MessageView = require_message_view.MessageView;
37
+ exports.MessageViewContent = require_message_view.MessageViewContent;
38
+ exports.MessageViewEmpty = require_message_view.MessageViewEmpty;
39
+ exports.MessageViewHeader = require_message_view.MessageViewHeader;
40
+ exports.WhatsappDashboard = require_whatsapp_dashboard.WhatsappDashboard;
41
+ exports.cn = require_utils.cn;
42
+ exports.getDisplayDate = require_utils.getDisplayDate;
43
+ exports.renderSlot = require_utils.renderSlot;
44
+ exports.useComposer = require_composer.useComposer;
45
+ exports.useComposerState = require_composer.useComposerState;
46
+ exports.useComposerValue = require_composer.useComposerValue;
47
+ exports.useFreeformMessageWindow = require_message_input.useFreeformMessageWindow;
48
+ exports.useOptionalWhatsappDashboard = require_whatsapp_dashboard.useOptionalWhatsappDashboard;
49
+ exports.useWhatsappDashboard = require_whatsapp_dashboard.useWhatsappDashboard;