@better-zap/react 0.2.0 → 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,96 @@
1
+ import React from "react";
2
+
3
+ //#region src/composer.d.ts
4
+ interface ComposerContextValue {
5
+ value: string;
6
+ setValue: (value: string) => void;
7
+ isSending: boolean;
8
+ disabled: boolean;
9
+ /** value.trim() is non-empty && !disabled && !isSending */
10
+ canSend: boolean;
11
+ /** null when no failed send is pending display */
12
+ error: unknown;
13
+ clearError: () => void;
14
+ /** Fire-and-forget; never rejects. See send() algorithm. */
15
+ send: () => void;
16
+ }
17
+ /**
18
+ * Everything except the live draft string. Changes only on boundary events
19
+ * (empty <-> non-empty, send lifecycle, errors), so consumers skip the
20
+ * per-keystroke re-render that `useComposer` implies.
21
+ */
22
+ interface ComposerStateContextValue extends Omit<ComposerContextValue, "value"> {
23
+ /** value.trim() is non-empty. Boundary-stable, unlike `value` itself. */
24
+ hasText: boolean;
25
+ }
26
+ /** The live draft string. Consumers re-render on every keystroke. */
27
+ declare function useComposerValue(): string;
28
+ /**
29
+ * Actions and boundary-stable flags without the live draft. Prefer this in
30
+ * children that don't display the text (send/attach buttons, error slots) so
31
+ * they don't re-render per keystroke.
32
+ */
33
+ declare function useComposerState(): ComposerStateContextValue;
34
+ /** Full composer context. Subscribes to the draft: re-renders per keystroke. */
35
+ declare function useComposer(): ComposerContextValue;
36
+ interface ComposerProps extends Omit<React.ComponentProps<"div">, "onSubmit" | "onError"> {
37
+ value?: string;
38
+ defaultValue?: string;
39
+ onValueChange?: (value: string) => void;
40
+ /** Required. Throw/reject to signal failure; the draft is preserved. */
41
+ onSubmit: (text: string) => void | Promise<void>;
42
+ onError?: (error: unknown, context: {
43
+ text: string;
44
+ }) => void;
45
+ disabled?: boolean;
46
+ }
47
+ declare function Composer({
48
+ value,
49
+ defaultValue,
50
+ onValueChange,
51
+ onSubmit,
52
+ onError,
53
+ disabled,
54
+ className,
55
+ children,
56
+ ...props
57
+ }: ComposerProps): React.JSX.Element;
58
+ interface ComposerTextareaProps extends React.ComponentProps<"textarea"> {
59
+ /** Enter sends (Shift+Enter = newline). Ignores IME composition. @default true */
60
+ submitOnEnter?: boolean;
61
+ }
62
+ declare function ComposerTextarea({
63
+ submitOnEnter,
64
+ className,
65
+ disabled: disabledProp,
66
+ onKeyDown,
67
+ onChange,
68
+ ...props
69
+ }: ComposerTextareaProps): React.JSX.Element;
70
+ type ComposerSendProps = React.ComponentProps<"button">;
71
+ declare function ComposerSend({
72
+ className,
73
+ disabled: disabledProp,
74
+ onClick,
75
+ children,
76
+ type,
77
+ ...props
78
+ }: ComposerSendProps): React.JSX.Element;
79
+ type ComposerButtonProps = React.ComponentProps<"button">;
80
+ declare function ComposerButton({
81
+ className,
82
+ disabled: disabledProp,
83
+ type,
84
+ ...props
85
+ }: ComposerButtonProps): React.JSX.Element;
86
+ interface ComposerErrorProps extends Omit<React.ComponentProps<"div">, "children"> {
87
+ /** Static node, or a function receiving the error. */
88
+ children?: React.ReactNode | ((error: unknown) => React.ReactNode);
89
+ }
90
+ declare function ComposerError({
91
+ children,
92
+ className,
93
+ ...props
94
+ }: ComposerErrorProps): React.JSX.Element | null;
95
+ //#endregion
96
+ export { Composer, ComposerButton, ComposerButtonProps, ComposerContextValue, ComposerError, ComposerErrorProps, ComposerProps, ComposerSend, ComposerSendProps, ComposerStateContextValue, ComposerTextarea, ComposerTextareaProps, useComposer, useComposerState, useComposerValue };
@@ -0,0 +1,211 @@
1
+ "use client";
2
+ import { cn } from "./utils.mjs";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { HugeiconsIcon } from "@hugeicons/react";
6
+ import { Sent02Icon } from "@hugeicons/core-free-icons";
7
+ //#region src/composer.tsx
8
+ const ComposerValueContext = createContext(null);
9
+ const ComposerStateContext = createContext(null);
10
+ /** The live draft string. Consumers re-render on every keystroke. */
11
+ function useComposerValue() {
12
+ const value = useContext(ComposerValueContext);
13
+ if (value === null) throw new Error("useComposerValue must be used within <Composer>");
14
+ return value;
15
+ }
16
+ /**
17
+ * Actions and boundary-stable flags without the live draft. Prefer this in
18
+ * children that don't display the text (send/attach buttons, error slots) so
19
+ * they don't re-render per keystroke.
20
+ */
21
+ function useComposerState() {
22
+ const ctx = useContext(ComposerStateContext);
23
+ if (!ctx) throw new Error("useComposerState must be used within <Composer>");
24
+ return ctx;
25
+ }
26
+ /** Full composer context. Subscribes to the draft: re-renders per keystroke. */
27
+ function useComposer() {
28
+ const ctx = useContext(ComposerStateContext);
29
+ const value = useContext(ComposerValueContext);
30
+ if (!ctx || value === null) throw new Error("useComposer must be used within <Composer>");
31
+ const { hasText: _hasText, ...rest } = ctx;
32
+ return {
33
+ ...rest,
34
+ value
35
+ };
36
+ }
37
+ const PILL_CLASS = "flex flex-wrap items-center gap-1 px-2 py-1 min-h-[52px] bg-white rounded-[24px] shadow-[0_1px_3px_rgba(11,20,26,0.12)]";
38
+ /**
39
+ * Owns draft/send state below the pill markup, so uncontrolled keystrokes
40
+ * re-render this provider and draft consumers only, never the pill div or
41
+ * children that stick to useComposerState.
42
+ */
43
+ function ComposerProvider({ value: valueProp, defaultValue, onValueChange, onSubmit, onError, disabled, children }) {
44
+ const isControlled = valueProp !== void 0;
45
+ const [internalValue, setInternalValue] = useState(defaultValue);
46
+ const [isSending, setIsSending] = useState(false);
47
+ const [error, setError] = useState(null);
48
+ const value = isControlled ? valueProp : internalValue;
49
+ const valueRef = useRef(value);
50
+ valueRef.current = value;
51
+ const disabledRef = useRef(disabled);
52
+ disabledRef.current = disabled;
53
+ const isSendingRef = useRef(false);
54
+ const onSubmitRef = useRef(onSubmit);
55
+ onSubmitRef.current = onSubmit;
56
+ const onErrorRef = useRef(onError);
57
+ onErrorRef.current = onError;
58
+ const onValueChangeRef = useRef(onValueChange);
59
+ onValueChangeRef.current = onValueChange;
60
+ const isControlledRef = useRef(isControlled);
61
+ isControlledRef.current = isControlled;
62
+ const setValue = useCallback((next) => {
63
+ if (!isControlledRef.current) setInternalValue(next);
64
+ onValueChangeRef.current?.(next);
65
+ }, []);
66
+ const clearError = useCallback(() => {
67
+ setError(null);
68
+ }, []);
69
+ const send = useCallback(() => {
70
+ (async () => {
71
+ const text = valueRef.current.trim();
72
+ if (!text || disabledRef.current || isSendingRef.current) return;
73
+ setError(null);
74
+ isSendingRef.current = true;
75
+ setIsSending(true);
76
+ try {
77
+ await onSubmitRef.current(text);
78
+ setValue("");
79
+ } catch (err) {
80
+ setError(err);
81
+ onErrorRef.current?.(err, { text });
82
+ } finally {
83
+ isSendingRef.current = false;
84
+ setIsSending(false);
85
+ }
86
+ })();
87
+ }, [setValue]);
88
+ const hasText = value.trim().length > 0;
89
+ const canSend = hasText && !disabled && !isSending;
90
+ const stateValue = useMemo(() => ({
91
+ setValue,
92
+ isSending,
93
+ disabled,
94
+ canSend,
95
+ hasText,
96
+ error,
97
+ clearError,
98
+ send
99
+ }), [
100
+ setValue,
101
+ isSending,
102
+ disabled,
103
+ canSend,
104
+ hasText,
105
+ error,
106
+ clearError,
107
+ send
108
+ ]);
109
+ return /* @__PURE__ */ jsx(ComposerStateContext.Provider, {
110
+ value: stateValue,
111
+ children: /* @__PURE__ */ jsx(ComposerValueContext.Provider, {
112
+ value,
113
+ children
114
+ })
115
+ });
116
+ }
117
+ function Composer({ value, defaultValue = "", onValueChange, onSubmit, onError, disabled = false, className, children, ...props }) {
118
+ return /* @__PURE__ */ jsx("div", {
119
+ className: cn(PILL_CLASS, className),
120
+ ...props,
121
+ children: /* @__PURE__ */ jsx(ComposerProvider, {
122
+ value,
123
+ defaultValue,
124
+ onValueChange,
125
+ onSubmit,
126
+ onError,
127
+ disabled,
128
+ children
129
+ })
130
+ });
131
+ }
132
+ const TEXTAREA_CLASS = "flex-1 bg-transparent border-none text-[15px] leading-[22px] text-[#111b21] resize-none focus:outline-none max-h-[120px] placeholder:text-[#8696a0]";
133
+ function ComposerTextarea({ submitOnEnter = true, className, disabled: disabledProp, onKeyDown, onChange, ...props }) {
134
+ const value = useComposerValue();
135
+ const { setValue, disabled, isSending, send } = useComposerState();
136
+ const textareaRef = useRef(null);
137
+ const isDisabled = disabled || isSending || !!disabledProp;
138
+ useEffect(() => {
139
+ const el = textareaRef.current;
140
+ if (!el) return;
141
+ el.style.height = "auto";
142
+ if (value === "") return;
143
+ el.style.height = `${el.scrollHeight}px`;
144
+ }, [value]);
145
+ const handleKeyDown = (e) => {
146
+ onKeyDown?.(e);
147
+ if (e.defaultPrevented) return;
148
+ if (!submitOnEnter) return;
149
+ if (e.key !== "Enter" || e.shiftKey) return;
150
+ if (e.nativeEvent.isComposing) return;
151
+ e.preventDefault();
152
+ send();
153
+ };
154
+ const handleChange = (e) => {
155
+ setValue(e.target.value);
156
+ onChange?.(e);
157
+ };
158
+ return /* @__PURE__ */ jsx("textarea", {
159
+ ref: textareaRef,
160
+ className: cn(TEXTAREA_CLASS, className),
161
+ name: "message",
162
+ autoComplete: "off",
163
+ rows: 1,
164
+ value,
165
+ onChange: handleChange,
166
+ onKeyDown: handleKeyDown,
167
+ disabled: isDisabled,
168
+ ...props
169
+ });
170
+ }
171
+ function ComposerSend({ className, disabled: disabledProp, onClick, children, type = "button", ...props }) {
172
+ const { canSend, send } = useComposerState();
173
+ const handleClick = (e) => {
174
+ send();
175
+ onClick?.(e);
176
+ };
177
+ return /* @__PURE__ */ jsx("button", {
178
+ type,
179
+ className: cn("flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[#00a884] text-white transition-colors hover:bg-[#008f72] disabled:opacity-40 disabled:cursor-not-allowed", className),
180
+ disabled: !canSend || !!disabledProp,
181
+ onClick: handleClick,
182
+ ...props,
183
+ children: children ?? /* @__PURE__ */ jsx(HugeiconsIcon, {
184
+ icon: Sent02Icon,
185
+ size: 20
186
+ })
187
+ });
188
+ }
189
+ function ComposerButton({ className, disabled: disabledProp, type = "button", ...props }) {
190
+ const { isSending } = useComposerState();
191
+ const isDisabled = disabledProp !== void 0 ? disabledProp : isSending;
192
+ return /* @__PURE__ */ jsx("button", {
193
+ type,
194
+ className: cn("p-2 rounded-full hover:bg-black/5 transition-colors disabled:opacity-40", className),
195
+ disabled: isDisabled,
196
+ ...props
197
+ });
198
+ }
199
+ function ComposerError({ children, className, ...props }) {
200
+ const { error } = useComposerState();
201
+ if (error == null) return null;
202
+ const content = typeof children === "function" ? children(error) : children;
203
+ return /* @__PURE__ */ jsx("div", {
204
+ role: "alert",
205
+ className: cn("w-full order-last", className),
206
+ ...props,
207
+ children: content
208
+ });
209
+ }
210
+ //#endregion
211
+ export { Composer, ComposerButton, ComposerError, ComposerSend, ComposerTextarea, useComposer, useComposerState, useComposerValue };
@@ -0,0 +1,100 @@
1
+ import React from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+ import { Conversation } from "better-zap";
4
+
5
+ //#region src/conversation-filter-chips.d.ts
6
+ type ConversationFilterValue = "all" | "unread";
7
+ interface ConversationFilterChipsProps {
8
+ value: ConversationFilterValue;
9
+ onValueChange: (value: ConversationFilterValue) => void;
10
+ unreadCount?: number;
11
+ className?: string;
12
+ labels?: {
13
+ all: string;
14
+ unread: string;
15
+ };
16
+ }
17
+ /**
18
+ * Memoized: with stable onValueChange and labels, typing in the search box
19
+ * doesn't re-render the chips.
20
+ */
21
+ declare const ConversationFilterChips: React.MemoExoticComponent<({
22
+ value,
23
+ onValueChange,
24
+ unreadCount,
25
+ className,
26
+ labels
27
+ }: ConversationFilterChipsProps) => react_jsx_runtime0.JSX.Element>;
28
+ //#endregion
29
+ //#region src/conversation-list.d.ts
30
+ interface ConversationListLabels {
31
+ searchPlaceholder: string;
32
+ searchLabel: string;
33
+ filterAll: string;
34
+ filterUnread: string;
35
+ loading: string;
36
+ error: string;
37
+ empty: string;
38
+ outgoingPrefix: string;
39
+ noPreview: string;
40
+ yesterday: string;
41
+ }
42
+ interface ConversationListProps extends Omit<React.ComponentProps<"div">, "onSelect"> {
43
+ conversations: Conversation[];
44
+ isLoading?: boolean;
45
+ isError?: boolean;
46
+ selectedConversationId?: string | null;
47
+ onSelect?: (id: string) => void;
48
+ search?: string;
49
+ defaultSearch?: string;
50
+ onSearchChange?: (value: string) => void;
51
+ filter?: ConversationFilterValue;
52
+ defaultFilter?: ConversationFilterValue;
53
+ onFilterChange?: (value: ConversationFilterValue) => void;
54
+ renderItem?: (conversation: Conversation, context: {
55
+ isSelected: boolean;
56
+ select: () => void;
57
+ }) => React.ReactNode;
58
+ renderAvatar?: (conversation: Conversation) => React.ReactNode;
59
+ formatTime?: (isoDate: string) => string;
60
+ labels?: Partial<ConversationListLabels>;
61
+ }
62
+ declare function ConversationList({
63
+ conversations,
64
+ isLoading,
65
+ isError,
66
+ selectedConversationId,
67
+ onSelect,
68
+ search: searchProp,
69
+ defaultSearch,
70
+ onSearchChange,
71
+ filter: filterProp,
72
+ defaultFilter,
73
+ onFilterChange,
74
+ renderItem,
75
+ renderAvatar,
76
+ formatTime: formatTimeProp,
77
+ labels: labelsProp,
78
+ className,
79
+ ...props
80
+ }: ConversationListProps): react_jsx_runtime0.JSX.Element;
81
+ interface ConversationItemProps extends React.ComponentProps<"button"> {
82
+ conversation: Conversation;
83
+ isSelected?: boolean;
84
+ avatar?: React.ReactNode;
85
+ outgoingPrefix?: string;
86
+ noPreviewLabel?: string;
87
+ formatTime?: (isoDate: string) => string;
88
+ }
89
+ declare function ConversationItem({
90
+ conversation,
91
+ isSelected,
92
+ avatar,
93
+ outgoingPrefix,
94
+ noPreviewLabel,
95
+ formatTime: formatTimeProp,
96
+ className,
97
+ ...props
98
+ }: ConversationItemProps): react_jsx_runtime0.JSX.Element;
99
+ //#endregion
100
+ export { ConversationListProps as a, ConversationFilterValue as c, ConversationListLabels as i, ConversationItemProps as n, ConversationFilterChips as o, ConversationList as r, ConversationFilterChipsProps as s, ConversationItem as t };
@@ -0,0 +1,100 @@
1
+ import React from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+ import { Conversation } from "better-zap";
4
+
5
+ //#region src/conversation-filter-chips.d.ts
6
+ type ConversationFilterValue = "all" | "unread";
7
+ interface ConversationFilterChipsProps {
8
+ value: ConversationFilterValue;
9
+ onValueChange: (value: ConversationFilterValue) => void;
10
+ unreadCount?: number;
11
+ className?: string;
12
+ labels?: {
13
+ all: string;
14
+ unread: string;
15
+ };
16
+ }
17
+ /**
18
+ * Memoized: with stable onValueChange and labels, typing in the search box
19
+ * doesn't re-render the chips.
20
+ */
21
+ declare const ConversationFilterChips: React.MemoExoticComponent<({
22
+ value,
23
+ onValueChange,
24
+ unreadCount,
25
+ className,
26
+ labels
27
+ }: ConversationFilterChipsProps) => react_jsx_runtime0.JSX.Element>;
28
+ //#endregion
29
+ //#region src/conversation-list.d.ts
30
+ interface ConversationListLabels {
31
+ searchPlaceholder: string;
32
+ searchLabel: string;
33
+ filterAll: string;
34
+ filterUnread: string;
35
+ loading: string;
36
+ error: string;
37
+ empty: string;
38
+ outgoingPrefix: string;
39
+ noPreview: string;
40
+ yesterday: string;
41
+ }
42
+ interface ConversationListProps extends Omit<React.ComponentProps<"div">, "onSelect"> {
43
+ conversations: Conversation[];
44
+ isLoading?: boolean;
45
+ isError?: boolean;
46
+ selectedConversationId?: string | null;
47
+ onSelect?: (id: string) => void;
48
+ search?: string;
49
+ defaultSearch?: string;
50
+ onSearchChange?: (value: string) => void;
51
+ filter?: ConversationFilterValue;
52
+ defaultFilter?: ConversationFilterValue;
53
+ onFilterChange?: (value: ConversationFilterValue) => void;
54
+ renderItem?: (conversation: Conversation, context: {
55
+ isSelected: boolean;
56
+ select: () => void;
57
+ }) => React.ReactNode;
58
+ renderAvatar?: (conversation: Conversation) => React.ReactNode;
59
+ formatTime?: (isoDate: string) => string;
60
+ labels?: Partial<ConversationListLabels>;
61
+ }
62
+ declare function ConversationList({
63
+ conversations,
64
+ isLoading,
65
+ isError,
66
+ selectedConversationId,
67
+ onSelect,
68
+ search: searchProp,
69
+ defaultSearch,
70
+ onSearchChange,
71
+ filter: filterProp,
72
+ defaultFilter,
73
+ onFilterChange,
74
+ renderItem,
75
+ renderAvatar,
76
+ formatTime: formatTimeProp,
77
+ labels: labelsProp,
78
+ className,
79
+ ...props
80
+ }: ConversationListProps): react_jsx_runtime0.JSX.Element;
81
+ interface ConversationItemProps extends React.ComponentProps<"button"> {
82
+ conversation: Conversation;
83
+ isSelected?: boolean;
84
+ avatar?: React.ReactNode;
85
+ outgoingPrefix?: string;
86
+ noPreviewLabel?: string;
87
+ formatTime?: (isoDate: string) => string;
88
+ }
89
+ declare function ConversationItem({
90
+ conversation,
91
+ isSelected,
92
+ avatar,
93
+ outgoingPrefix,
94
+ noPreviewLabel,
95
+ formatTime: formatTimeProp,
96
+ className,
97
+ ...props
98
+ }: ConversationItemProps): react_jsx_runtime0.JSX.Element;
99
+ //#endregion
100
+ export { ConversationListProps as a, ConversationFilterValue as c, ConversationListLabels as i, ConversationItemProps as n, ConversationFilterChips as o, ConversationList as r, ConversationFilterChipsProps as s, ConversationItem as t };