whatsapp-ui-react 0.0.3 → 0.0.5

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/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  export type * from './components/Chat';
2
- export type { ChatHandle } from './components/Chat/Chat';
3
2
  export { Chat } from './components/Chat';
3
+ export type { ChatHandle } from './components/Chat/Chat';
4
+ export type * from './components/History';
5
+ export { History } from './components/History';
6
+ export type * from './components/Animated';
7
+ export { Animated } from './components/Animated';
4
8
  export type * from './components/Message';
5
- export { Audio, Contact, Emoji, Event, File, Gif, Image, Location, Message, Poll, Sticker, Text, Video, Voice, useMessage, } from './components/Message';
9
+ export { Audio, Contact, Emoji, Event, File, Gif, Image, Location, Message, Poll, Sticker, Text, useMessage, Video, Voice, } from './components/Message';
6
10
  export type * from './components/Reply';
7
11
  export { Reply } from './components/Reply';
8
12
  export { useMessages } from './hooks/useMessages';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,mBAAmB,CAAA;AACtC,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAExC,mBAAmB,sBAAsB,CAAA;AACzC,OAAO,EACL,KAAK,EACL,OAAO,EACP,KAAK,EACL,KAAK,EACL,IAAI,EACJ,GAAG,EACH,KAAK,EACL,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,KAAK,EACL,KAAK,EACL,UAAU,GACX,MAAM,sBAAsB,CAAA;AAE7B,mBAAmB,oBAAoB,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,mBAAmB,kBAAkB,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,gBAAgB,CAAA;AAEvB,mBAAmB,mBAAmB,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AACxC,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,mBAAmB,sBAAsB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAE9C,mBAAmB,uBAAuB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAEhD,mBAAmB,sBAAsB,CAAA;AACzC,OAAO,EACL,KAAK,EACL,OAAO,EACP,KAAK,EACL,KAAK,EACL,IAAI,EACJ,GAAG,EACH,KAAK,EACL,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,UAAU,EACV,KAAK,EACL,KAAK,GACN,MAAM,sBAAsB,CAAA;AAE7B,mBAAmB,oBAAoB,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEjD,mBAAmB,kBAAkB,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,EAAE,EAAE,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -3357,7 +3357,7 @@ function StatusIcon({ status, className }) {
3357
3357
  return /* @__PURE__ */ jsx(
3358
3358
  StatusDoubleCheckIcon,
3359
3359
  {
3360
- className: cn("size-4", status === "read" && "text-wa-read", className)
3360
+ className: cn("size-4", status === "read" && "text-wa-teal", className)
3361
3361
  }
3362
3362
  );
3363
3363
  }
@@ -3393,8 +3393,8 @@ function Message({
3393
3393
  "div",
3394
3394
  {
3395
3395
  className: cn(
3396
- "relative w-fit rounded-wa-bubble px-3 py-1.5 shadow-md",
3397
- isOut ? "bg-wa-bubble-out text-wa-text-primary" : "bg-wa-bubble-in text-wa-text-primary",
3396
+ "relative w-fit rounded-[0.525rem] px-3 py-1.5 shadow-md",
3397
+ isOut ? "bg-wa-bubble-out text-wa-text" : "bg-wa-bubble-in text-wa-text",
3398
3398
  top && isOut && "rounded-tr-none",
3399
3399
  top && !isOut && "rounded-tl-none"
3400
3400
  ),
@@ -3649,9 +3649,9 @@ function Audio({ src, duration, fileName }) {
3649
3649
  if (progress > 0) setHasPlayed(true);
3650
3650
  }, [progress]);
3651
3651
  const displayDuration = totalDuration > 0 ? fmtTime(remaining) : duration ?? "0:00";
3652
- const trackColor = isOut ? "bg-wa-waveform-out" : "bg-wa-waveform-in";
3653
- const trackColorFaint = isOut ? "bg-wa-waveform-out/40" : "bg-wa-waveform-in/40";
3654
- const dotColor = hasPlayed || isOut ? "bg-wa-waveform-out" : "bg-wa-waveform-in";
3652
+ const trackColor = isOut ? "bg-wa-teal" : "bg-wa-text-secondary";
3653
+ const trackColorFaint = isOut ? "bg-wa-teal/40" : "bg-wa-text-secondary/40";
3654
+ const dotColor = hasPlayed || isOut ? "bg-wa-teal" : "bg-wa-text-secondary";
3655
3655
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3656
3656
  /* @__PURE__ */ jsx("audio", { ref: audioRef, src, preload: "metadata" }),
3657
3657
  /* @__PURE__ */ jsxs("div", { className: "flex h-13.75 w-84 min-w-84 shrink-0 items-center", children: [
@@ -3738,9 +3738,12 @@ const CANVAS_HEIGHT = 30;
3738
3738
  const BAR_MAX_HEIGHT = 26;
3739
3739
  const BAR_WIDTH = 3;
3740
3740
  const SLOT_WIDTH = CANVAS_WIDTH / BAR_COUNT;
3741
- function readCssVar(name) {
3742
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
3743
- }
3741
+ const COLORS = {
3742
+ waveformOut: "#53bdeb",
3743
+ waveformIn: "#8696a0",
3744
+ waveformOutFaint: "rgba(83, 189, 235, 0.4)",
3745
+ waveformInFaint: "rgba(134, 150, 160, 0.4)"
3746
+ };
3744
3747
  function Waveform({ bars, progress, isOut, hasPlayed, seek }) {
3745
3748
  const canvasRef = useRef(null);
3746
3749
  useEffect(() => {
@@ -3749,8 +3752,8 @@ function Waveform({ bars, progress, isOut, hasPlayed, seek }) {
3749
3752
  const ctx = canvas.getContext("2d");
3750
3753
  if (!ctx) return;
3751
3754
  ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
3752
- const colorPlayed = isOut ? readCssVar("--color-wa-waveform-out") : readCssVar("--color-wa-waveform-in");
3753
- const colorUnplayed = isOut ? readCssVar("--color-wa-waveform-out-faint") : readCssVar("--color-wa-waveform-in-faint");
3755
+ const colorPlayed = isOut ? COLORS.waveformOut : COLORS.waveformIn;
3756
+ const colorUnplayed = isOut ? COLORS.waveformOutFaint : COLORS.waveformInFaint;
3754
3757
  bars.forEach((amplitude, i) => {
3755
3758
  const level = Math.max(1, amplitude);
3756
3759
  const barHeight = Math.round(level / 10 * BAR_MAX_HEIGHT);
@@ -3778,7 +3781,7 @@ function Waveform({ bars, progress, isOut, hasPlayed, seek }) {
3778
3781
  "aria-hidden": "true",
3779
3782
  className: cn(
3780
3783
  "pointer-events-none absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full",
3781
- hasPlayed || isOut ? "bg-wa-waveform-out" : "bg-wa-waveform-in"
3784
+ hasPlayed || isOut ? "bg-wa-teal" : "bg-wa-text-secondary"
3782
3785
  ),
3783
3786
  style: { left: `${progress * 100}%` }
3784
3787
  }
@@ -3896,7 +3899,7 @@ function Gif({ className }) {
3896
3899
  return /* @__PURE__ */ jsx("div", { className: cn("w-[336px]", className) });
3897
3900
  }
3898
3901
  function Image({ src, alt = "", className }) {
3899
- return /* @__PURE__ */ jsx("div", { className: cn("w-[336px]", className), children: /* @__PURE__ */ jsx("img", { src, alt, className: "w-full rounded-wa-bubble object-cover" }) });
3902
+ return /* @__PURE__ */ jsx("div", { className: cn("w-[336px]", className), children: /* @__PURE__ */ jsx("img", { src, alt, className: "w-full rounded-[0.525rem] object-cover" }) });
3900
3903
  }
3901
3904
  let _counter = 0;
3902
3905
  function uid() {
@@ -3926,13 +3929,13 @@ function ChatHeader({ className, name, avatarUrl, subtitle }) {
3926
3929
  "div",
3927
3930
  {
3928
3931
  className: cn(
3929
- "flex items-center gap-3 bg-wa-header px-4 py-3 shadow-[0_1px_4px_rgba(0,0,0,0.15)]",
3932
+ "flex items-center gap-3 bg-wa-bg px-4 py-3 shadow-[0_1px_4px_rgba(0,0,0,0.15)]",
3930
3933
  className
3931
3934
  ),
3932
3935
  children: [
3933
- /* @__PURE__ */ jsx("span", { className: "shrink-0", children: avatarUrl ? /* @__PURE__ */ jsx("img", { src: avatarUrl, alt: name, className: "size-10 rounded-full object-cover" }) : /* @__PURE__ */ jsx("span", { className: "flex size-10 items-center justify-center rounded-full bg-wa-avatar text-sm font-medium text-wa-text-primary", children: initials }) }),
3936
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: avatarUrl ? /* @__PURE__ */ jsx("img", { src: avatarUrl, alt: name, className: "size-10 rounded-full object-cover" }) : /* @__PURE__ */ jsx("span", { className: "flex size-10 items-center justify-center rounded-full bg-wa-avatar text-sm font-medium text-wa-text", children: initials }) }),
3934
3937
  /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
3935
- /* @__PURE__ */ jsx("span", { className: "truncate text-[15px] font-medium text-wa-text-primary", children: name }),
3938
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[15px] font-medium text-wa-text", children: name }),
3936
3939
  subtitle && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-wa-text-secondary", children: subtitle })
3937
3940
  ] })
3938
3941
  ]
@@ -3950,7 +3953,7 @@ function ActionButton({
3950
3953
  type: "button",
3951
3954
  onClick,
3952
3955
  "aria-label": label,
3953
- className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full text-wa-icon transition-colors hover:text-wa-text-primary",
3956
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full text-wa-icon transition-colors hover:text-wa-text",
3954
3957
  children
3955
3958
  }
3956
3959
  );
@@ -3986,7 +3989,7 @@ function InputfieldActions({
3986
3989
  type: "button",
3987
3990
  onClick: onMicClick,
3988
3991
  "aria-label": "Record voice message",
3989
- className: "group inline-flex size-8 shrink-0 items-center justify-center rounded-full text-wa-icon transition-colors hover:bg-wa-teal hover:text-black",
3992
+ className: "group inline-flex size-8 shrink-0 items-center justify-center rounded-full text-wa-icon transition-colors hover:bg-wa-hover hover:text-black",
3990
3993
  children: [
3991
3994
  /* @__PURE__ */ jsx(MicOutlineIcon, { className: "size-5 group-hover:hidden" }),
3992
3995
  /* @__PURE__ */ jsx(MicFillIcon, { className: "hidden size-5 group-hover:block" })
@@ -4111,7 +4114,7 @@ function getDisplayDate(date) {
4111
4114
  }
4112
4115
  function DayDivider({ className, date }) {
4113
4116
  const displayDate = getDisplayDate(date.toISOString());
4114
- return /* @__PURE__ */ jsx("div", { className: cn("flex justify-center py-3", className), children: /* @__PURE__ */ jsx("span", { className: "rounded-wa-divider bg-wa-divider px-3 py-1 text-[13px] font-medium text-wa-text-secondary shadow-sm", children: displayDate }) });
4117
+ return /* @__PURE__ */ jsx("div", { className: cn("flex justify-center py-3", className), children: /* @__PURE__ */ jsx("span", { className: "rounded-[0.325rem] bg-wa-divider px-3 py-1 text-[13px] font-medium text-wa-text-secondary shadow-sm", children: displayDate }) });
4115
4118
  }
4116
4119
  function MessageList({ className, messages }) {
4117
4120
  const dayGroups = groupMessagesByDay(messages);
@@ -4154,15 +4157,22 @@ const Chat$1 = React.forwardRef(function Chat2({
4154
4157
  onEmojiClick,
4155
4158
  onAttachClick,
4156
4159
  onCameraClick,
4157
- onMicClick
4160
+ onMicClick,
4161
+ theme
4158
4162
  }, ref) {
4159
4163
  const [messages, setMessages] = React.useState(messageHistory ?? []);
4160
4164
  const scrollRef = React.useRef(null);
4165
+ const contentRef = React.useRef(null);
4161
4166
  React.useEffect(() => {
4162
- if (scrollRef.current) {
4163
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
4164
- }
4165
- }, [messages]);
4167
+ const content = contentRef.current;
4168
+ const scroll = scrollRef.current;
4169
+ if (!content || !scroll) return;
4170
+ const ro = new ResizeObserver(() => {
4171
+ scroll.scrollTop = scroll.scrollHeight;
4172
+ });
4173
+ ro.observe(content);
4174
+ return () => ro.disconnect();
4175
+ }, []);
4166
4176
  function sendMessage(text) {
4167
4177
  const trimmed = text.trim();
4168
4178
  if (!trimmed) return;
@@ -4200,6 +4210,7 @@ const Chat$1 = React.forwardRef(function Chat2({
4200
4210
  {
4201
4211
  className: cn("flex h-full min-h-0 flex-col", isDefaultBg ? "bg-wa-bg" : "", className),
4202
4212
  style: isDefaultBg ? void 0 : bgStyle,
4213
+ ...theme ? { "data-wa-theme": theme } : {},
4203
4214
  children: [
4204
4215
  /* @__PURE__ */ jsx(
4205
4216
  ChatHeader,
@@ -4217,7 +4228,17 @@ const Chat$1 = React.forwardRef(function Chat2({
4217
4228
  style: { backgroundImage: `url(${backgroundUrl})` }
4218
4229
  }
4219
4230
  ),
4220
- /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "scrollbar-wa flex-1 overflow-y-auto py-2 px-12", children: messages.length > 0 ? /* @__PURE__ */ jsx(MessageList, { messages }) : children }),
4231
+ /* @__PURE__ */ jsx(
4232
+ "div",
4233
+ {
4234
+ ref: scrollRef,
4235
+ className: "flex-1 overflow-y-auto py-2 px-12 [scrollbar-width:thin] [scrollbar-color:rgba(255,255,255,0.1)_transparent] [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-[rgba(255,255,255,0.18)] [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb:hover]:bg-[rgba(255,255,255,0.3)]",
4236
+ children: /* @__PURE__ */ jsxs("div", { ref: contentRef, children: [
4237
+ children,
4238
+ messages.length > 0 && /* @__PURE__ */ jsx(MessageList, { messages })
4239
+ ] })
4240
+ }
4241
+ ),
4221
4242
  showInputfield && /* @__PURE__ */ jsx(
4222
4243
  Inputfield,
4223
4244
  {
@@ -4249,6 +4270,114 @@ const ChatParts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
4249
4270
  Root: Chat$1
4250
4271
  }, Symbol.toStringTag, { value: "Module" }));
4251
4272
  const Chat = Object.assign(Chat$1, ChatParts);
4273
+ function Animated({ delay = 0, children, className }) {
4274
+ const [mounted, setMounted] = React.useState(delay === 0);
4275
+ const [visible, setVisible] = React.useState(false);
4276
+ React.useEffect(() => {
4277
+ if (delay === 0) {
4278
+ const raf = requestAnimationFrame(() => setVisible(true));
4279
+ return () => cancelAnimationFrame(raf);
4280
+ }
4281
+ const timer = setTimeout(() => setMounted(true), delay);
4282
+ return () => clearTimeout(timer);
4283
+ }, [delay]);
4284
+ React.useLayoutEffect(() => {
4285
+ if (mounted && delay !== 0) {
4286
+ const raf = requestAnimationFrame(() => setVisible(true));
4287
+ return () => cancelAnimationFrame(raf);
4288
+ }
4289
+ }, [mounted, delay]);
4290
+ if (!mounted) return /* @__PURE__ */ jsx(Fragment, {});
4291
+ return /* @__PURE__ */ jsx(
4292
+ "div",
4293
+ {
4294
+ className: cn(
4295
+ "transition-all duration-300 ease-out",
4296
+ visible ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0",
4297
+ className
4298
+ ),
4299
+ children
4300
+ }
4301
+ );
4302
+ }
4303
+ function History({ children, className }) {
4304
+ const childArray = React.Children.toArray(children ?? []);
4305
+ const result = [];
4306
+ let lastGroupKey = void 0;
4307
+ let lastDate = null;
4308
+ const today = /* @__PURE__ */ new Date();
4309
+ let hasAnyMessage = false;
4310
+ function extractMessageInfo(node) {
4311
+ if (!React.isValidElement(node)) return null;
4312
+ const el = node;
4313
+ if (el.type === Message) {
4314
+ return {
4315
+ msgProps: el.props,
4316
+ isAnimated: false,
4317
+ msgEl: el
4318
+ };
4319
+ }
4320
+ if (el.type === Animated) {
4321
+ const innerChildren = React.Children.toArray(
4322
+ el.props.children ?? []
4323
+ );
4324
+ const innerMsg = innerChildren.find(
4325
+ (c) => React.isValidElement(c) && c.type === Message
4326
+ );
4327
+ if (innerMsg) {
4328
+ return {
4329
+ msgProps: innerMsg.props,
4330
+ isAnimated: true,
4331
+ animatedEl: el,
4332
+ msgEl: innerMsg
4333
+ };
4334
+ }
4335
+ }
4336
+ return null;
4337
+ }
4338
+ for (const child of childArray) {
4339
+ const info = extractMessageInfo(child);
4340
+ if (!info) {
4341
+ result.push(child);
4342
+ continue;
4343
+ }
4344
+ hasAnyMessage = true;
4345
+ const { msgProps, isAnimated } = info;
4346
+ const groupKey = msgProps.senderId ?? msgProps.direction;
4347
+ const currentDate = toDate(msgProps.timestamp);
4348
+ const isNewDay = currentDate !== null && (lastDate === null || !isSameCalendarDay(currentDate, lastDate));
4349
+ if (isNewDay && currentDate) {
4350
+ result.push(/* @__PURE__ */ jsx(DayDivider, { date: currentDate }, `divider-${currentDate.toISOString()}`));
4351
+ }
4352
+ const isNewGroup = lastGroupKey === void 0 || groupKey !== lastGroupKey || isNewDay;
4353
+ if (isAnimated) {
4354
+ const { animatedEl, msgEl } = info;
4355
+ const newMsg = React.cloneElement(msgEl, {
4356
+ top: isNewGroup
4357
+ });
4358
+ result.push(
4359
+ React.cloneElement(animatedEl, {
4360
+ children: newMsg
4361
+ })
4362
+ );
4363
+ } else {
4364
+ result.push(
4365
+ React.cloneElement(info.msgEl, {
4366
+ top: isNewGroup
4367
+ })
4368
+ );
4369
+ }
4370
+ lastGroupKey = groupKey;
4371
+ if (currentDate) lastDate = currentDate;
4372
+ }
4373
+ if (!hasAnyMessage) {
4374
+ result.push(/* @__PURE__ */ jsx(DayDivider, { date: today }, "history-today-divider"));
4375
+ }
4376
+ if (className) {
4377
+ return /* @__PURE__ */ jsx("div", { className: cn(className), children: result });
4378
+ }
4379
+ return /* @__PURE__ */ jsx(Fragment, { children: result });
4380
+ }
4252
4381
  function extractTextFromNode(node) {
4253
4382
  if (node === null || node === void 0) return "";
4254
4383
  if (typeof node === "string") return node;
@@ -4302,6 +4431,7 @@ function useMessages(callback) {
4302
4431
  }, [messages]);
4303
4432
  }
4304
4433
  export {
4434
+ Animated,
4305
4435
  Audio,
4306
4436
  Chat,
4307
4437
  Contact,
@@ -4309,6 +4439,7 @@ export {
4309
4439
  Event,
4310
4440
  File,
4311
4441
  Gif,
4442
+ History,
4312
4443
  Image,
4313
4444
  Location,
4314
4445
  Message,