@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.
- package/dist/bubble.cjs +93 -0
- package/dist/bubble.d.cts +61 -0
- package/dist/bubble.d.mts +61 -0
- package/dist/bubble.mjs +89 -0
- package/dist/composer.cjs +220 -0
- package/dist/composer.d.cts +96 -0
- package/dist/composer.d.mts +96 -0
- package/dist/composer.mjs +211 -0
- package/dist/conversation-list-BlhJhDsc.d.cts +100 -0
- package/dist/conversation-list-BwQ4oK2J.d.mts +100 -0
- package/dist/conversation-list-C0EmReS-.mjs +293 -0
- package/dist/conversation-list-CVWdddJh.cjs +311 -0
- package/dist/conversation-list.cjs +6 -0
- package/dist/conversation-list.d.cts +2 -0
- package/dist/conversation-list.d.mts +2 -0
- package/dist/conversation-list.mjs +5 -0
- package/dist/index.cjs +49 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.mjs +11 -0
- package/dist/message-bubble.cjs +116 -0
- package/dist/message-bubble.d.cts +38 -0
- package/dist/message-bubble.d.mts +38 -0
- package/dist/message-bubble.mjs +114 -0
- package/dist/message-input-CkaL6fb4.mjs +173 -0
- package/dist/message-input-DcEzHQ9t.cjs +191 -0
- package/dist/message-input.cjs +6 -0
- package/dist/message-input.d.cts +51 -0
- package/dist/message-input.d.mts +51 -0
- package/dist/message-input.mjs +5 -0
- package/dist/message-view.cjs +273 -0
- package/dist/message-view.d.cts +109 -0
- package/dist/message-view.d.mts +109 -0
- package/dist/message-view.mjs +266 -0
- package/dist/message.cjs +50 -0
- package/dist/message.d.cts +40 -0
- package/dist/message.d.mts +40 -0
- package/dist/message.mjs +43 -0
- package/dist/tailwind.css +33 -0
- package/dist/utils-Bp9IahGG.cjs +91 -0
- package/dist/utils.cjs +5 -0
- package/dist/utils.d.cts +20 -0
- package/dist/utils.d.mts +20 -0
- package/dist/utils.mjs +45 -0
- package/dist/whatsapp-dashboard.cjs +64 -0
- package/dist/whatsapp-dashboard.d.cts +33 -0
- package/dist/whatsapp-dashboard.d.mts +33 -0
- package/dist/whatsapp-dashboard.mjs +60 -0
- package/dist/wpp-bg.webp +0 -0
- package/package.json +1 -1
package/dist/bubble.cjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_utils = require("./utils-Bp9IahGG.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
react = require_utils.__toESM(react);
|
|
5
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
|
+
//#region src/bubble.tsx
|
|
7
|
+
const bubbleVariants = (0, require("class-variance-authority").cva)("relative w-fit max-w-full rounded-[7.5px] px-[9px] py-[6px] text-[14.2px] leading-[19px] shadow-[0_1px_0.5px_rgba(11,20,26,0.13)]", {
|
|
8
|
+
variants: { variant: {
|
|
9
|
+
default: "bg-white text-[#111b21]",
|
|
10
|
+
primary: "bg-[#d9fdd3] text-[#111b21]",
|
|
11
|
+
destructive: "bg-[#fdd8d5] text-[#111b21]",
|
|
12
|
+
outline: "border border-gray-300 bg-transparent text-[#111b21] shadow-none",
|
|
13
|
+
muted: "bg-gray-50 text-gray-500"
|
|
14
|
+
} },
|
|
15
|
+
defaultVariants: { variant: "default" }
|
|
16
|
+
});
|
|
17
|
+
const tailCornerByAlign = {
|
|
18
|
+
start: "rounded-tl-none",
|
|
19
|
+
end: "rounded-tr-none"
|
|
20
|
+
};
|
|
21
|
+
const tailColorByVariant = {
|
|
22
|
+
default: "text-white",
|
|
23
|
+
primary: "text-[#d9fdd3]",
|
|
24
|
+
destructive: "text-[#fdd8d5]",
|
|
25
|
+
outline: "text-transparent",
|
|
26
|
+
muted: "text-gray-50"
|
|
27
|
+
};
|
|
28
|
+
/** WhatsApp's bubble tail, drawn outside the top corner of the bubble. */
|
|
29
|
+
function BubbleTail({ align, variant }) {
|
|
30
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
31
|
+
"aria-hidden": true,
|
|
32
|
+
className: require_utils.cn("absolute top-0 block h-[13px] w-2", align === "start" ? "-left-2 -scale-x-100" : "-right-2", tailColorByVariant[variant]),
|
|
33
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
34
|
+
viewBox: "0 0 8 13",
|
|
35
|
+
width: "8",
|
|
36
|
+
height: "13",
|
|
37
|
+
fill: "none",
|
|
38
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
39
|
+
fill: "#0b141a",
|
|
40
|
+
opacity: "0.13",
|
|
41
|
+
d: "M5.188 1H0v11.193l6.467-8.625C7.526 2.156 6.958 1 5.188 1Z"
|
|
42
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
43
|
+
fill: "currentColor",
|
|
44
|
+
d: "M5.188 0H0v11.193l6.467-8.625C7.526 1.156 6.958 0 5.188 0Z"
|
|
45
|
+
})]
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function Bubble({ variant = "default", align = "start", tail = true, className, children, ...props }) {
|
|
50
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
51
|
+
"data-variant": variant,
|
|
52
|
+
"data-align": align,
|
|
53
|
+
className: require_utils.cn(bubbleVariants({ variant }), tail && tailCornerByAlign[align], className),
|
|
54
|
+
...props,
|
|
55
|
+
children: [tail && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BubbleTail, {
|
|
56
|
+
align,
|
|
57
|
+
variant
|
|
58
|
+
}), children]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function BubbleContent({ render, className, children, ...props }) {
|
|
62
|
+
const contentClassName = require_utils.cn("whitespace-pre-wrap break-words select-text focus-visible:outline-2 focus-visible:outline-offset-2", className);
|
|
63
|
+
if (render) return require_utils.renderSlot(render, {
|
|
64
|
+
className: contentClassName,
|
|
65
|
+
...props,
|
|
66
|
+
children
|
|
67
|
+
});
|
|
68
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
69
|
+
className: contentClassName,
|
|
70
|
+
...props,
|
|
71
|
+
children
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function BubbleReactions({ side = "bottom", align = "end", className, ...props }) {
|
|
75
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
76
|
+
"data-side": side,
|
|
77
|
+
"data-align": align,
|
|
78
|
+
className: require_utils.cn("absolute z-10 flex items-center gap-0.5 rounded-full border border-gray-200 bg-white px-1.5 py-0.5 text-xs shadow-sm", side === "bottom" ? "-bottom-2.5" : "-top-2.5", align === "start" ? "left-1" : "right-1", className),
|
|
79
|
+
...props
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function BubbleGroup({ align = "start", className, ...props }) {
|
|
83
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
84
|
+
"data-align": align,
|
|
85
|
+
className: require_utils.cn("flex w-full flex-col gap-0.5", align === "start" ? "items-start" : "items-end", className),
|
|
86
|
+
...props
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
exports.Bubble = Bubble;
|
|
91
|
+
exports.BubbleContent = BubbleContent;
|
|
92
|
+
exports.BubbleGroup = BubbleGroup;
|
|
93
|
+
exports.BubbleReactions = BubbleReactions;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/bubble.d.ts
|
|
4
|
+
type BubbleAlign = "start" | "end";
|
|
5
|
+
type BubbleVariant = "default" | "primary" | "destructive" | "outline" | "muted";
|
|
6
|
+
interface BubbleProps extends React.ComponentProps<"div"> {
|
|
7
|
+
/** Visual treatment. Presentational only — never domain terms. @default "default" */
|
|
8
|
+
variant?: BubbleVariant;
|
|
9
|
+
/** Which side the corner tail sits on. @default "start" */
|
|
10
|
+
align?: BubbleAlign;
|
|
11
|
+
/**
|
|
12
|
+
* Renders WhatsApp's corner tail and squares that corner. Show it on the
|
|
13
|
+
* first bubble of a group (or a lone bubble), hide it on follow-ups.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
tail?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare function Bubble({
|
|
19
|
+
variant,
|
|
20
|
+
align,
|
|
21
|
+
tail,
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: BubbleProps): React.JSX.Element;
|
|
26
|
+
interface BubbleContentProps extends React.ComponentProps<"div"> {
|
|
27
|
+
/**
|
|
28
|
+
* Element-only polymorphism (Base UI style). When provided, BubbleContent
|
|
29
|
+
* clones this element instead of rendering a <div>, merging props per renderSlot.
|
|
30
|
+
*/
|
|
31
|
+
render?: React.ReactElement<Record<string, unknown>>;
|
|
32
|
+
}
|
|
33
|
+
declare function BubbleContent({
|
|
34
|
+
render,
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: BubbleContentProps): React.JSX.Element;
|
|
39
|
+
interface BubbleReactionsProps extends React.ComponentProps<"div"> {
|
|
40
|
+
/** @default "bottom" */
|
|
41
|
+
side?: "top" | "bottom";
|
|
42
|
+
/** @default "end" */
|
|
43
|
+
align?: BubbleAlign;
|
|
44
|
+
}
|
|
45
|
+
declare function BubbleReactions({
|
|
46
|
+
side,
|
|
47
|
+
align,
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}: BubbleReactionsProps): React.JSX.Element;
|
|
51
|
+
interface BubbleGroupProps extends React.ComponentProps<"div"> {
|
|
52
|
+
/** @default "start" */
|
|
53
|
+
align?: BubbleAlign;
|
|
54
|
+
}
|
|
55
|
+
declare function BubbleGroup({
|
|
56
|
+
align,
|
|
57
|
+
className,
|
|
58
|
+
...props
|
|
59
|
+
}: BubbleGroupProps): React.JSX.Element;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { Bubble, BubbleAlign, BubbleContent, BubbleContentProps, BubbleGroup, BubbleGroupProps, BubbleProps, BubbleReactions, BubbleReactionsProps, BubbleVariant };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/bubble.d.ts
|
|
4
|
+
type BubbleAlign = "start" | "end";
|
|
5
|
+
type BubbleVariant = "default" | "primary" | "destructive" | "outline" | "muted";
|
|
6
|
+
interface BubbleProps extends React.ComponentProps<"div"> {
|
|
7
|
+
/** Visual treatment. Presentational only — never domain terms. @default "default" */
|
|
8
|
+
variant?: BubbleVariant;
|
|
9
|
+
/** Which side the corner tail sits on. @default "start" */
|
|
10
|
+
align?: BubbleAlign;
|
|
11
|
+
/**
|
|
12
|
+
* Renders WhatsApp's corner tail and squares that corner. Show it on the
|
|
13
|
+
* first bubble of a group (or a lone bubble), hide it on follow-ups.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
tail?: boolean;
|
|
17
|
+
}
|
|
18
|
+
declare function Bubble({
|
|
19
|
+
variant,
|
|
20
|
+
align,
|
|
21
|
+
tail,
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: BubbleProps): React.JSX.Element;
|
|
26
|
+
interface BubbleContentProps extends React.ComponentProps<"div"> {
|
|
27
|
+
/**
|
|
28
|
+
* Element-only polymorphism (Base UI style). When provided, BubbleContent
|
|
29
|
+
* clones this element instead of rendering a <div>, merging props per renderSlot.
|
|
30
|
+
*/
|
|
31
|
+
render?: React.ReactElement<Record<string, unknown>>;
|
|
32
|
+
}
|
|
33
|
+
declare function BubbleContent({
|
|
34
|
+
render,
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: BubbleContentProps): React.JSX.Element;
|
|
39
|
+
interface BubbleReactionsProps extends React.ComponentProps<"div"> {
|
|
40
|
+
/** @default "bottom" */
|
|
41
|
+
side?: "top" | "bottom";
|
|
42
|
+
/** @default "end" */
|
|
43
|
+
align?: BubbleAlign;
|
|
44
|
+
}
|
|
45
|
+
declare function BubbleReactions({
|
|
46
|
+
side,
|
|
47
|
+
align,
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}: BubbleReactionsProps): React.JSX.Element;
|
|
51
|
+
interface BubbleGroupProps extends React.ComponentProps<"div"> {
|
|
52
|
+
/** @default "start" */
|
|
53
|
+
align?: BubbleAlign;
|
|
54
|
+
}
|
|
55
|
+
declare function BubbleGroup({
|
|
56
|
+
align,
|
|
57
|
+
className,
|
|
58
|
+
...props
|
|
59
|
+
}: BubbleGroupProps): React.JSX.Element;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { Bubble, BubbleAlign, BubbleContent, BubbleContentProps, BubbleGroup, BubbleGroupProps, BubbleProps, BubbleReactions, BubbleReactionsProps, BubbleVariant };
|
package/dist/bubble.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { cn, renderSlot } from "./utils.mjs";
|
|
2
|
+
import "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
//#region src/bubble.tsx
|
|
6
|
+
const bubbleVariants = cva("relative w-fit max-w-full rounded-[7.5px] px-[9px] py-[6px] text-[14.2px] leading-[19px] shadow-[0_1px_0.5px_rgba(11,20,26,0.13)]", {
|
|
7
|
+
variants: { variant: {
|
|
8
|
+
default: "bg-white text-[#111b21]",
|
|
9
|
+
primary: "bg-[#d9fdd3] text-[#111b21]",
|
|
10
|
+
destructive: "bg-[#fdd8d5] text-[#111b21]",
|
|
11
|
+
outline: "border border-gray-300 bg-transparent text-[#111b21] shadow-none",
|
|
12
|
+
muted: "bg-gray-50 text-gray-500"
|
|
13
|
+
} },
|
|
14
|
+
defaultVariants: { variant: "default" }
|
|
15
|
+
});
|
|
16
|
+
const tailCornerByAlign = {
|
|
17
|
+
start: "rounded-tl-none",
|
|
18
|
+
end: "rounded-tr-none"
|
|
19
|
+
};
|
|
20
|
+
const tailColorByVariant = {
|
|
21
|
+
default: "text-white",
|
|
22
|
+
primary: "text-[#d9fdd3]",
|
|
23
|
+
destructive: "text-[#fdd8d5]",
|
|
24
|
+
outline: "text-transparent",
|
|
25
|
+
muted: "text-gray-50"
|
|
26
|
+
};
|
|
27
|
+
/** WhatsApp's bubble tail, drawn outside the top corner of the bubble. */
|
|
28
|
+
function BubbleTail({ align, variant }) {
|
|
29
|
+
return /* @__PURE__ */ jsx("span", {
|
|
30
|
+
"aria-hidden": true,
|
|
31
|
+
className: cn("absolute top-0 block h-[13px] w-2", align === "start" ? "-left-2 -scale-x-100" : "-right-2", tailColorByVariant[variant]),
|
|
32
|
+
children: /* @__PURE__ */ jsxs("svg", {
|
|
33
|
+
viewBox: "0 0 8 13",
|
|
34
|
+
width: "8",
|
|
35
|
+
height: "13",
|
|
36
|
+
fill: "none",
|
|
37
|
+
children: [/* @__PURE__ */ jsx("path", {
|
|
38
|
+
fill: "#0b141a",
|
|
39
|
+
opacity: "0.13",
|
|
40
|
+
d: "M5.188 1H0v11.193l6.467-8.625C7.526 2.156 6.958 1 5.188 1Z"
|
|
41
|
+
}), /* @__PURE__ */ jsx("path", {
|
|
42
|
+
fill: "currentColor",
|
|
43
|
+
d: "M5.188 0H0v11.193l6.467-8.625C7.526 1.156 6.958 0 5.188 0Z"
|
|
44
|
+
})]
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function Bubble({ variant = "default", align = "start", tail = true, className, children, ...props }) {
|
|
49
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
50
|
+
"data-variant": variant,
|
|
51
|
+
"data-align": align,
|
|
52
|
+
className: cn(bubbleVariants({ variant }), tail && tailCornerByAlign[align], className),
|
|
53
|
+
...props,
|
|
54
|
+
children: [tail && /* @__PURE__ */ jsx(BubbleTail, {
|
|
55
|
+
align,
|
|
56
|
+
variant
|
|
57
|
+
}), children]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function BubbleContent({ render, className, children, ...props }) {
|
|
61
|
+
const contentClassName = cn("whitespace-pre-wrap break-words select-text focus-visible:outline-2 focus-visible:outline-offset-2", className);
|
|
62
|
+
if (render) return renderSlot(render, {
|
|
63
|
+
className: contentClassName,
|
|
64
|
+
...props,
|
|
65
|
+
children
|
|
66
|
+
});
|
|
67
|
+
return /* @__PURE__ */ jsx("div", {
|
|
68
|
+
className: contentClassName,
|
|
69
|
+
...props,
|
|
70
|
+
children
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function BubbleReactions({ side = "bottom", align = "end", className, ...props }) {
|
|
74
|
+
return /* @__PURE__ */ jsx("div", {
|
|
75
|
+
"data-side": side,
|
|
76
|
+
"data-align": align,
|
|
77
|
+
className: cn("absolute z-10 flex items-center gap-0.5 rounded-full border border-gray-200 bg-white px-1.5 py-0.5 text-xs shadow-sm", side === "bottom" ? "-bottom-2.5" : "-top-2.5", align === "start" ? "left-1" : "right-1", className),
|
|
78
|
+
...props
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function BubbleGroup({ align = "start", className, ...props }) {
|
|
82
|
+
return /* @__PURE__ */ jsx("div", {
|
|
83
|
+
"data-align": align,
|
|
84
|
+
className: cn("flex w-full flex-col gap-0.5", align === "start" ? "items-start" : "items-end", className),
|
|
85
|
+
...props
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { Bubble, BubbleContent, BubbleGroup, BubbleReactions };
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_utils = require("./utils-Bp9IahGG.cjs");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
react = require_utils.__toESM(react);
|
|
6
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
let _hugeicons_react = require("@hugeicons/react");
|
|
8
|
+
let _hugeicons_core_free_icons = require("@hugeicons/core-free-icons");
|
|
9
|
+
//#region src/composer.tsx
|
|
10
|
+
const ComposerValueContext = (0, react.createContext)(null);
|
|
11
|
+
const ComposerStateContext = (0, react.createContext)(null);
|
|
12
|
+
/** The live draft string. Consumers re-render on every keystroke. */
|
|
13
|
+
function useComposerValue() {
|
|
14
|
+
const value = (0, react.useContext)(ComposerValueContext);
|
|
15
|
+
if (value === null) throw new Error("useComposerValue must be used within <Composer>");
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Actions and boundary-stable flags without the live draft. Prefer this in
|
|
20
|
+
* children that don't display the text (send/attach buttons, error slots) so
|
|
21
|
+
* they don't re-render per keystroke.
|
|
22
|
+
*/
|
|
23
|
+
function useComposerState() {
|
|
24
|
+
const ctx = (0, react.useContext)(ComposerStateContext);
|
|
25
|
+
if (!ctx) throw new Error("useComposerState must be used within <Composer>");
|
|
26
|
+
return ctx;
|
|
27
|
+
}
|
|
28
|
+
/** Full composer context. Subscribes to the draft: re-renders per keystroke. */
|
|
29
|
+
function useComposer() {
|
|
30
|
+
const ctx = (0, react.useContext)(ComposerStateContext);
|
|
31
|
+
const value = (0, react.useContext)(ComposerValueContext);
|
|
32
|
+
if (!ctx || value === null) throw new Error("useComposer must be used within <Composer>");
|
|
33
|
+
const { hasText: _hasText, ...rest } = ctx;
|
|
34
|
+
return {
|
|
35
|
+
...rest,
|
|
36
|
+
value
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
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)]";
|
|
40
|
+
/**
|
|
41
|
+
* Owns draft/send state below the pill markup, so uncontrolled keystrokes
|
|
42
|
+
* re-render this provider and draft consumers only, never the pill div or
|
|
43
|
+
* children that stick to useComposerState.
|
|
44
|
+
*/
|
|
45
|
+
function ComposerProvider({ value: valueProp, defaultValue, onValueChange, onSubmit, onError, disabled, children }) {
|
|
46
|
+
const isControlled = valueProp !== void 0;
|
|
47
|
+
const [internalValue, setInternalValue] = (0, react.useState)(defaultValue);
|
|
48
|
+
const [isSending, setIsSending] = (0, react.useState)(false);
|
|
49
|
+
const [error, setError] = (0, react.useState)(null);
|
|
50
|
+
const value = isControlled ? valueProp : internalValue;
|
|
51
|
+
const valueRef = (0, react.useRef)(value);
|
|
52
|
+
valueRef.current = value;
|
|
53
|
+
const disabledRef = (0, react.useRef)(disabled);
|
|
54
|
+
disabledRef.current = disabled;
|
|
55
|
+
const isSendingRef = (0, react.useRef)(false);
|
|
56
|
+
const onSubmitRef = (0, react.useRef)(onSubmit);
|
|
57
|
+
onSubmitRef.current = onSubmit;
|
|
58
|
+
const onErrorRef = (0, react.useRef)(onError);
|
|
59
|
+
onErrorRef.current = onError;
|
|
60
|
+
const onValueChangeRef = (0, react.useRef)(onValueChange);
|
|
61
|
+
onValueChangeRef.current = onValueChange;
|
|
62
|
+
const isControlledRef = (0, react.useRef)(isControlled);
|
|
63
|
+
isControlledRef.current = isControlled;
|
|
64
|
+
const setValue = (0, react.useCallback)((next) => {
|
|
65
|
+
if (!isControlledRef.current) setInternalValue(next);
|
|
66
|
+
onValueChangeRef.current?.(next);
|
|
67
|
+
}, []);
|
|
68
|
+
const clearError = (0, react.useCallback)(() => {
|
|
69
|
+
setError(null);
|
|
70
|
+
}, []);
|
|
71
|
+
const send = (0, react.useCallback)(() => {
|
|
72
|
+
(async () => {
|
|
73
|
+
const text = valueRef.current.trim();
|
|
74
|
+
if (!text || disabledRef.current || isSendingRef.current) return;
|
|
75
|
+
setError(null);
|
|
76
|
+
isSendingRef.current = true;
|
|
77
|
+
setIsSending(true);
|
|
78
|
+
try {
|
|
79
|
+
await onSubmitRef.current(text);
|
|
80
|
+
setValue("");
|
|
81
|
+
} catch (err) {
|
|
82
|
+
setError(err);
|
|
83
|
+
onErrorRef.current?.(err, { text });
|
|
84
|
+
} finally {
|
|
85
|
+
isSendingRef.current = false;
|
|
86
|
+
setIsSending(false);
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
}, [setValue]);
|
|
90
|
+
const hasText = value.trim().length > 0;
|
|
91
|
+
const canSend = hasText && !disabled && !isSending;
|
|
92
|
+
const stateValue = (0, react.useMemo)(() => ({
|
|
93
|
+
setValue,
|
|
94
|
+
isSending,
|
|
95
|
+
disabled,
|
|
96
|
+
canSend,
|
|
97
|
+
hasText,
|
|
98
|
+
error,
|
|
99
|
+
clearError,
|
|
100
|
+
send
|
|
101
|
+
}), [
|
|
102
|
+
setValue,
|
|
103
|
+
isSending,
|
|
104
|
+
disabled,
|
|
105
|
+
canSend,
|
|
106
|
+
hasText,
|
|
107
|
+
error,
|
|
108
|
+
clearError,
|
|
109
|
+
send
|
|
110
|
+
]);
|
|
111
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerStateContext.Provider, {
|
|
112
|
+
value: stateValue,
|
|
113
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerValueContext.Provider, {
|
|
114
|
+
value,
|
|
115
|
+
children
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function Composer({ value, defaultValue = "", onValueChange, onSubmit, onError, disabled = false, className, children, ...props }) {
|
|
120
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
121
|
+
className: require_utils.cn(PILL_CLASS, className),
|
|
122
|
+
...props,
|
|
123
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerProvider, {
|
|
124
|
+
value,
|
|
125
|
+
defaultValue,
|
|
126
|
+
onValueChange,
|
|
127
|
+
onSubmit,
|
|
128
|
+
onError,
|
|
129
|
+
disabled,
|
|
130
|
+
children
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
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]";
|
|
135
|
+
function ComposerTextarea({ submitOnEnter = true, className, disabled: disabledProp, onKeyDown, onChange, ...props }) {
|
|
136
|
+
const value = useComposerValue();
|
|
137
|
+
const { setValue, disabled, isSending, send } = useComposerState();
|
|
138
|
+
const textareaRef = (0, react.useRef)(null);
|
|
139
|
+
const isDisabled = disabled || isSending || !!disabledProp;
|
|
140
|
+
(0, react.useEffect)(() => {
|
|
141
|
+
const el = textareaRef.current;
|
|
142
|
+
if (!el) return;
|
|
143
|
+
el.style.height = "auto";
|
|
144
|
+
if (value === "") return;
|
|
145
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
146
|
+
}, [value]);
|
|
147
|
+
const handleKeyDown = (e) => {
|
|
148
|
+
onKeyDown?.(e);
|
|
149
|
+
if (e.defaultPrevented) return;
|
|
150
|
+
if (!submitOnEnter) return;
|
|
151
|
+
if (e.key !== "Enter" || e.shiftKey) return;
|
|
152
|
+
if (e.nativeEvent.isComposing) return;
|
|
153
|
+
e.preventDefault();
|
|
154
|
+
send();
|
|
155
|
+
};
|
|
156
|
+
const handleChange = (e) => {
|
|
157
|
+
setValue(e.target.value);
|
|
158
|
+
onChange?.(e);
|
|
159
|
+
};
|
|
160
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
161
|
+
ref: textareaRef,
|
|
162
|
+
className: require_utils.cn(TEXTAREA_CLASS, className),
|
|
163
|
+
name: "message",
|
|
164
|
+
autoComplete: "off",
|
|
165
|
+
rows: 1,
|
|
166
|
+
value,
|
|
167
|
+
onChange: handleChange,
|
|
168
|
+
onKeyDown: handleKeyDown,
|
|
169
|
+
disabled: isDisabled,
|
|
170
|
+
...props
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function ComposerSend({ className, disabled: disabledProp, onClick, children, type = "button", ...props }) {
|
|
174
|
+
const { canSend, send } = useComposerState();
|
|
175
|
+
const handleClick = (e) => {
|
|
176
|
+
send();
|
|
177
|
+
onClick?.(e);
|
|
178
|
+
};
|
|
179
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
180
|
+
type,
|
|
181
|
+
className: require_utils.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),
|
|
182
|
+
disabled: !canSend || !!disabledProp,
|
|
183
|
+
onClick: handleClick,
|
|
184
|
+
...props,
|
|
185
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_hugeicons_react.HugeiconsIcon, {
|
|
186
|
+
icon: _hugeicons_core_free_icons.Sent02Icon,
|
|
187
|
+
size: 20
|
|
188
|
+
})
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function ComposerButton({ className, disabled: disabledProp, type = "button", ...props }) {
|
|
192
|
+
const { isSending } = useComposerState();
|
|
193
|
+
const isDisabled = disabledProp !== void 0 ? disabledProp : isSending;
|
|
194
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
195
|
+
type,
|
|
196
|
+
className: require_utils.cn("p-2 rounded-full hover:bg-black/5 transition-colors disabled:opacity-40", className),
|
|
197
|
+
disabled: isDisabled,
|
|
198
|
+
...props
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function ComposerError({ children, className, ...props }) {
|
|
202
|
+
const { error } = useComposerState();
|
|
203
|
+
if (error == null) return null;
|
|
204
|
+
const content = typeof children === "function" ? children(error) : children;
|
|
205
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
206
|
+
role: "alert",
|
|
207
|
+
className: require_utils.cn("w-full order-last", className),
|
|
208
|
+
...props,
|
|
209
|
+
children: content
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
exports.Composer = Composer;
|
|
214
|
+
exports.ComposerButton = ComposerButton;
|
|
215
|
+
exports.ComposerError = ComposerError;
|
|
216
|
+
exports.ComposerSend = ComposerSend;
|
|
217
|
+
exports.ComposerTextarea = ComposerTextarea;
|
|
218
|
+
exports.useComposer = useComposer;
|
|
219
|
+
exports.useComposerState = useComposerState;
|
|
220
|
+
exports.useComposerValue = useComposerValue;
|
|
@@ -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 };
|