mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Self-review of the light/dark sweep surfaced neutral colors that lived in raw inline-style / <style jsx> / gradient values (so the className sweep correctly skipped them) and a few contrast/affordance nits: - reply-quote: adapt to the outbound (primary) bubble via onPrimary so the quote/author stay legible on the violet fill in light mode; wire it from message-bubble. - dashboard SVG chrome -> tokens: conversations-chart gridline/crosshair, pipeline-donut background ring, automation-builder dot-grid gradient. - members-tab viewer chip: bg-card -> bg-transparent (outline) so it doesn't blend into a card surface in light mode. - tag-manager / pipeline-settings color-swatch selection rings: raw slate/white -> var(--popover)/var(--foreground) so they flip. - pipeline-board scrollbar (<style jsx>): slate rgb -> var tokens. - activity-feed zebra stripe: bg-card/40 -> bg-muted/40 (survives light). - forgot-password / import-modal: restore real hover (dead no-op hovers). - themed-toaster: gate theme via useSyncExternalStore to avoid a hydration mismatch on sonner's theme attribute (no setState-in-effect). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { X } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import type { Message } from "@/types";
|
|
|
|
interface ReplyQuoteProps {
|
|
/** Sender label of the quoted message: "You" for our own messages,
|
|
* contact name for customer-sent messages. Caller resolves this — the
|
|
* quote component doesn't see the parent Message. */
|
|
authorLabel: string;
|
|
/** Compact text preview. Falls back to a placeholder for media types. */
|
|
preview: string;
|
|
/** Present → renders the composer-chip variant with an X button. Absent →
|
|
* renders the embedded-in-bubble variant. */
|
|
onDismiss?: () => void;
|
|
/** True when embedded inside an outbound (primary-filled) bubble, so the
|
|
* quote must read against the primary surface rather than the neutral
|
|
* foreground — otherwise it goes low-contrast in light mode. */
|
|
onPrimary?: boolean;
|
|
}
|
|
|
|
export function ReplyQuote({
|
|
authorLabel,
|
|
preview,
|
|
onDismiss,
|
|
onPrimary = false,
|
|
}: ReplyQuoteProps) {
|
|
const isChip = !!onDismiss;
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"flex items-start gap-2 border-l-2 px-2 py-1",
|
|
onPrimary ? "border-primary-foreground/50" : "border-primary",
|
|
isChip
|
|
? "rounded-md bg-muted/80"
|
|
: onPrimary
|
|
? "mb-1.5 rounded-md bg-primary-foreground/15"
|
|
: "mb-1.5 rounded-md bg-background/20",
|
|
)}
|
|
>
|
|
<div className="min-w-0 flex-1 overflow-hidden">
|
|
<div
|
|
className={cn(
|
|
"truncate text-[11px] font-medium",
|
|
onPrimary ? "text-primary-foreground" : "text-primary",
|
|
)}
|
|
>
|
|
{authorLabel}
|
|
</div>
|
|
{/* Wrap the preview instead of truncating to a single line.
|
|
* `truncate` (white-space: nowrap) forced the quote onto one
|
|
* impossibly-wide line and — because the parent flex chain
|
|
* lacked `min-w-0` at every step — pushed the entire inbox
|
|
* layout wider, shoving the contact sidebar off-screen.
|
|
* `break-words` also wraps long URLs that have no whitespace
|
|
* to break on. Issue #165. */}
|
|
<div className="whitespace-pre-wrap break-words text-xs text-foreground/80">
|
|
{preview}
|
|
</div>
|
|
</div>
|
|
{onDismiss && (
|
|
<button
|
|
type="button"
|
|
onClick={onDismiss}
|
|
aria-label="Cancel reply"
|
|
className="flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
>
|
|
<X className="h-3.5 w-3.5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Build the one-line preview text shown inside a reply quote. */
|
|
export function buildReplyPreview(message: Message): string {
|
|
if (message.content_text) return message.content_text;
|
|
switch (message.content_type) {
|
|
case "image":
|
|
return "[Image]";
|
|
case "video":
|
|
return "[Video]";
|
|
case "audio":
|
|
return "[Audio]";
|
|
case "document":
|
|
return "[Document]";
|
|
case "location":
|
|
return "[Location]";
|
|
case "template":
|
|
return "[Template]";
|
|
default:
|
|
return "[Message]";
|
|
}
|
|
}
|