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.cjs CHANGED
@@ -3360,7 +3360,7 @@ function StatusIcon({ status, className }) {
3360
3360
  return /* @__PURE__ */ jsxRuntime.jsx(
3361
3361
  StatusDoubleCheckIcon,
3362
3362
  {
3363
- className: cn("size-4", status === "read" && "text-wa-read", className)
3363
+ className: cn("size-4", status === "read" && "text-wa-teal", className)
3364
3364
  }
3365
3365
  );
3366
3366
  }
@@ -3396,8 +3396,8 @@ function Message({
3396
3396
  "div",
3397
3397
  {
3398
3398
  className: cn(
3399
- "relative w-fit rounded-wa-bubble px-3 py-1.5 shadow-md",
3400
- isOut ? "bg-wa-bubble-out text-wa-text-primary" : "bg-wa-bubble-in text-wa-text-primary",
3399
+ "relative w-fit rounded-[0.525rem] px-3 py-1.5 shadow-md",
3400
+ isOut ? "bg-wa-bubble-out text-wa-text" : "bg-wa-bubble-in text-wa-text",
3401
3401
  top && isOut && "rounded-tr-none",
3402
3402
  top && !isOut && "rounded-tl-none"
3403
3403
  ),
@@ -3652,9 +3652,9 @@ function Audio({ src, duration, fileName }) {
3652
3652
  if (progress > 0) setHasPlayed(true);
3653
3653
  }, [progress]);
3654
3654
  const displayDuration = totalDuration > 0 ? fmtTime(remaining) : duration ?? "0:00";
3655
- const trackColor = isOut ? "bg-wa-waveform-out" : "bg-wa-waveform-in";
3656
- const trackColorFaint = isOut ? "bg-wa-waveform-out/40" : "bg-wa-waveform-in/40";
3657
- const dotColor = hasPlayed || isOut ? "bg-wa-waveform-out" : "bg-wa-waveform-in";
3655
+ const trackColor = isOut ? "bg-wa-teal" : "bg-wa-text-secondary";
3656
+ const trackColorFaint = isOut ? "bg-wa-teal/40" : "bg-wa-text-secondary/40";
3657
+ const dotColor = hasPlayed || isOut ? "bg-wa-teal" : "bg-wa-text-secondary";
3658
3658
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3659
3659
  /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, src, preload: "metadata" }),
3660
3660
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-13.75 w-84 min-w-84 shrink-0 items-center", children: [
@@ -3741,9 +3741,12 @@ const CANVAS_HEIGHT = 30;
3741
3741
  const BAR_MAX_HEIGHT = 26;
3742
3742
  const BAR_WIDTH = 3;
3743
3743
  const SLOT_WIDTH = CANVAS_WIDTH / BAR_COUNT;
3744
- function readCssVar(name) {
3745
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
3746
- }
3744
+ const COLORS = {
3745
+ waveformOut: "#53bdeb",
3746
+ waveformIn: "#8696a0",
3747
+ waveformOutFaint: "rgba(83, 189, 235, 0.4)",
3748
+ waveformInFaint: "rgba(134, 150, 160, 0.4)"
3749
+ };
3747
3750
  function Waveform({ bars, progress, isOut, hasPlayed, seek }) {
3748
3751
  const canvasRef = React.useRef(null);
3749
3752
  React.useEffect(() => {
@@ -3752,8 +3755,8 @@ function Waveform({ bars, progress, isOut, hasPlayed, seek }) {
3752
3755
  const ctx = canvas.getContext("2d");
3753
3756
  if (!ctx) return;
3754
3757
  ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
3755
- const colorPlayed = isOut ? readCssVar("--color-wa-waveform-out") : readCssVar("--color-wa-waveform-in");
3756
- const colorUnplayed = isOut ? readCssVar("--color-wa-waveform-out-faint") : readCssVar("--color-wa-waveform-in-faint");
3758
+ const colorPlayed = isOut ? COLORS.waveformOut : COLORS.waveformIn;
3759
+ const colorUnplayed = isOut ? COLORS.waveformOutFaint : COLORS.waveformInFaint;
3757
3760
  bars.forEach((amplitude, i) => {
3758
3761
  const level = Math.max(1, amplitude);
3759
3762
  const barHeight = Math.round(level / 10 * BAR_MAX_HEIGHT);
@@ -3781,7 +3784,7 @@ function Waveform({ bars, progress, isOut, hasPlayed, seek }) {
3781
3784
  "aria-hidden": "true",
3782
3785
  className: cn(
3783
3786
  "pointer-events-none absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full",
3784
- hasPlayed || isOut ? "bg-wa-waveform-out" : "bg-wa-waveform-in"
3787
+ hasPlayed || isOut ? "bg-wa-teal" : "bg-wa-text-secondary"
3785
3788
  ),
3786
3789
  style: { left: `${progress * 100}%` }
3787
3790
  }
@@ -3899,7 +3902,7 @@ function Gif({ className }) {
3899
3902
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("w-[336px]", className) });
3900
3903
  }
3901
3904
  function Image({ src, alt = "", className }) {
3902
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("w-[336px]", className), children: /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, className: "w-full rounded-wa-bubble object-cover" }) });
3905
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("w-[336px]", className), children: /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, className: "w-full rounded-[0.525rem] object-cover" }) });
3903
3906
  }
3904
3907
  let _counter = 0;
3905
3908
  function uid() {
@@ -3929,13 +3932,13 @@ function ChatHeader({ className, name, avatarUrl, subtitle }) {
3929
3932
  "div",
3930
3933
  {
3931
3934
  className: cn(
3932
- "flex items-center gap-3 bg-wa-header px-4 py-3 shadow-[0_1px_4px_rgba(0,0,0,0.15)]",
3935
+ "flex items-center gap-3 bg-wa-bg px-4 py-3 shadow-[0_1px_4px_rgba(0,0,0,0.15)]",
3933
3936
  className
3934
3937
  ),
3935
3938
  children: [
3936
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", children: avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: avatarUrl, alt: name, className: "size-10 rounded-full object-cover" }) : /* @__PURE__ */ jsxRuntime.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 }) }),
3939
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", children: avatarUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: avatarUrl, alt: name, className: "size-10 rounded-full object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-10 items-center justify-center rounded-full bg-wa-avatar text-sm font-medium text-wa-text", children: initials }) }),
3937
3940
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
3938
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[15px] font-medium text-wa-text-primary", children: name }),
3941
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[15px] font-medium text-wa-text", children: name }),
3939
3942
  subtitle && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-wa-text-secondary", children: subtitle })
3940
3943
  ] })
3941
3944
  ]
@@ -3953,7 +3956,7 @@ function ActionButton({
3953
3956
  type: "button",
3954
3957
  onClick,
3955
3958
  "aria-label": label,
3956
- className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full text-wa-icon transition-colors hover:text-wa-text-primary",
3959
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full text-wa-icon transition-colors hover:text-wa-text",
3957
3960
  children
3958
3961
  }
3959
3962
  );
@@ -3989,7 +3992,7 @@ function InputfieldActions({
3989
3992
  type: "button",
3990
3993
  onClick: onMicClick,
3991
3994
  "aria-label": "Record voice message",
3992
- 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",
3995
+ 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",
3993
3996
  children: [
3994
3997
  /* @__PURE__ */ jsxRuntime.jsx(MicOutlineIcon, { className: "size-5 group-hover:hidden" }),
3995
3998
  /* @__PURE__ */ jsxRuntime.jsx(MicFillIcon, { className: "hidden size-5 group-hover:block" })
@@ -4114,7 +4117,7 @@ function getDisplayDate(date) {
4114
4117
  }
4115
4118
  function DayDivider({ className, date }) {
4116
4119
  const displayDate = getDisplayDate(date.toISOString());
4117
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex justify-center py-3", className), children: /* @__PURE__ */ jsxRuntime.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 }) });
4120
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex justify-center py-3", className), children: /* @__PURE__ */ jsxRuntime.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 }) });
4118
4121
  }
4119
4122
  function MessageList({ className, messages }) {
4120
4123
  const dayGroups = groupMessagesByDay(messages);
@@ -4157,15 +4160,22 @@ const Chat$1 = React.forwardRef(function Chat2({
4157
4160
  onEmojiClick,
4158
4161
  onAttachClick,
4159
4162
  onCameraClick,
4160
- onMicClick
4163
+ onMicClick,
4164
+ theme
4161
4165
  }, ref) {
4162
4166
  const [messages, setMessages] = React.useState(messageHistory ?? []);
4163
4167
  const scrollRef = React.useRef(null);
4168
+ const contentRef = React.useRef(null);
4164
4169
  React.useEffect(() => {
4165
- if (scrollRef.current) {
4166
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
4167
- }
4168
- }, [messages]);
4170
+ const content = contentRef.current;
4171
+ const scroll = scrollRef.current;
4172
+ if (!content || !scroll) return;
4173
+ const ro = new ResizeObserver(() => {
4174
+ scroll.scrollTop = scroll.scrollHeight;
4175
+ });
4176
+ ro.observe(content);
4177
+ return () => ro.disconnect();
4178
+ }, []);
4169
4179
  function sendMessage(text) {
4170
4180
  const trimmed = text.trim();
4171
4181
  if (!trimmed) return;
@@ -4203,6 +4213,7 @@ const Chat$1 = React.forwardRef(function Chat2({
4203
4213
  {
4204
4214
  className: cn("flex h-full min-h-0 flex-col", isDefaultBg ? "bg-wa-bg" : "", className),
4205
4215
  style: isDefaultBg ? void 0 : bgStyle,
4216
+ ...theme ? { "data-wa-theme": theme } : {},
4206
4217
  children: [
4207
4218
  /* @__PURE__ */ jsxRuntime.jsx(
4208
4219
  ChatHeader,
@@ -4220,7 +4231,17 @@ const Chat$1 = React.forwardRef(function Chat2({
4220
4231
  style: { backgroundImage: `url(${backgroundUrl})` }
4221
4232
  }
4222
4233
  ),
4223
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "scrollbar-wa flex-1 overflow-y-auto py-2 px-12", children: messages.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MessageList, { messages }) : children }),
4234
+ /* @__PURE__ */ jsxRuntime.jsx(
4235
+ "div",
4236
+ {
4237
+ ref: scrollRef,
4238
+ 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)]",
4239
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: contentRef, children: [
4240
+ children,
4241
+ messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(MessageList, { messages })
4242
+ ] })
4243
+ }
4244
+ ),
4224
4245
  showInputfield && /* @__PURE__ */ jsxRuntime.jsx(
4225
4246
  Inputfield,
4226
4247
  {
@@ -4252,6 +4273,114 @@ const ChatParts = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePro
4252
4273
  Root: Chat$1
4253
4274
  }, Symbol.toStringTag, { value: "Module" }));
4254
4275
  const Chat = Object.assign(Chat$1, ChatParts);
4276
+ function Animated({ delay = 0, children, className }) {
4277
+ const [mounted, setMounted] = React.useState(delay === 0);
4278
+ const [visible, setVisible] = React.useState(false);
4279
+ React.useEffect(() => {
4280
+ if (delay === 0) {
4281
+ const raf = requestAnimationFrame(() => setVisible(true));
4282
+ return () => cancelAnimationFrame(raf);
4283
+ }
4284
+ const timer = setTimeout(() => setMounted(true), delay);
4285
+ return () => clearTimeout(timer);
4286
+ }, [delay]);
4287
+ React.useLayoutEffect(() => {
4288
+ if (mounted && delay !== 0) {
4289
+ const raf = requestAnimationFrame(() => setVisible(true));
4290
+ return () => cancelAnimationFrame(raf);
4291
+ }
4292
+ }, [mounted, delay]);
4293
+ if (!mounted) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
4294
+ return /* @__PURE__ */ jsxRuntime.jsx(
4295
+ "div",
4296
+ {
4297
+ className: cn(
4298
+ "transition-all duration-300 ease-out",
4299
+ visible ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0",
4300
+ className
4301
+ ),
4302
+ children
4303
+ }
4304
+ );
4305
+ }
4306
+ function History({ children, className }) {
4307
+ const childArray = React.Children.toArray(children ?? []);
4308
+ const result = [];
4309
+ let lastGroupKey = void 0;
4310
+ let lastDate = null;
4311
+ const today = /* @__PURE__ */ new Date();
4312
+ let hasAnyMessage = false;
4313
+ function extractMessageInfo(node) {
4314
+ if (!React.isValidElement(node)) return null;
4315
+ const el = node;
4316
+ if (el.type === Message) {
4317
+ return {
4318
+ msgProps: el.props,
4319
+ isAnimated: false,
4320
+ msgEl: el
4321
+ };
4322
+ }
4323
+ if (el.type === Animated) {
4324
+ const innerChildren = React.Children.toArray(
4325
+ el.props.children ?? []
4326
+ );
4327
+ const innerMsg = innerChildren.find(
4328
+ (c) => React.isValidElement(c) && c.type === Message
4329
+ );
4330
+ if (innerMsg) {
4331
+ return {
4332
+ msgProps: innerMsg.props,
4333
+ isAnimated: true,
4334
+ animatedEl: el,
4335
+ msgEl: innerMsg
4336
+ };
4337
+ }
4338
+ }
4339
+ return null;
4340
+ }
4341
+ for (const child of childArray) {
4342
+ const info = extractMessageInfo(child);
4343
+ if (!info) {
4344
+ result.push(child);
4345
+ continue;
4346
+ }
4347
+ hasAnyMessage = true;
4348
+ const { msgProps, isAnimated } = info;
4349
+ const groupKey = msgProps.senderId ?? msgProps.direction;
4350
+ const currentDate = toDate(msgProps.timestamp);
4351
+ const isNewDay = currentDate !== null && (lastDate === null || !isSameCalendarDay(currentDate, lastDate));
4352
+ if (isNewDay && currentDate) {
4353
+ result.push(/* @__PURE__ */ jsxRuntime.jsx(DayDivider, { date: currentDate }, `divider-${currentDate.toISOString()}`));
4354
+ }
4355
+ const isNewGroup = lastGroupKey === void 0 || groupKey !== lastGroupKey || isNewDay;
4356
+ if (isAnimated) {
4357
+ const { animatedEl, msgEl } = info;
4358
+ const newMsg = React.cloneElement(msgEl, {
4359
+ top: isNewGroup
4360
+ });
4361
+ result.push(
4362
+ React.cloneElement(animatedEl, {
4363
+ children: newMsg
4364
+ })
4365
+ );
4366
+ } else {
4367
+ result.push(
4368
+ React.cloneElement(info.msgEl, {
4369
+ top: isNewGroup
4370
+ })
4371
+ );
4372
+ }
4373
+ lastGroupKey = groupKey;
4374
+ if (currentDate) lastDate = currentDate;
4375
+ }
4376
+ if (!hasAnyMessage) {
4377
+ result.push(/* @__PURE__ */ jsxRuntime.jsx(DayDivider, { date: today }, "history-today-divider"));
4378
+ }
4379
+ if (className) {
4380
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(className), children: result });
4381
+ }
4382
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: result });
4383
+ }
4255
4384
  function extractTextFromNode(node) {
4256
4385
  if (node === null || node === void 0) return "";
4257
4386
  if (typeof node === "string") return node;
@@ -4304,6 +4433,7 @@ function useMessages(callback) {
4304
4433
  for (const m of newMessages) callbackRef.current(m);
4305
4434
  }, [messages]);
4306
4435
  }
4436
+ exports.Animated = Animated;
4307
4437
  exports.Audio = Audio;
4308
4438
  exports.Chat = Chat;
4309
4439
  exports.Contact = Contact;
@@ -4311,6 +4441,7 @@ exports.Emoji = Emoji;
4311
4441
  exports.Event = Event;
4312
4442
  exports.File = File;
4313
4443
  exports.Gif = Gif;
4444
+ exports.History = History;
4314
4445
  exports.Image = Image;
4315
4446
  exports.Location = Location;
4316
4447
  exports.Message = Message;