Files
alirez.brandCursor a47ec493c5 feat: add version history drawer with release notes
Introduce a footer-triggered Version History drawer with categorized release notes, hardcoded release data, and semver tracking at v0.3.0. Also ships blog blockquote/table styling, site-wide typography, home page image updates, header refinements, and related bug fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 02:46:20 +03:30

108 lines
3.6 KiB
TypeScript

"use client";
import { Bot, User } from "lucide-react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { type, typeSpacing } from "@/lib/typography";
import type { ChatMessage } from "./types";
interface ChatMessageListProps {
messages: ChatMessage[];
isTyping?: boolean;
bottomRef?: React.RefObject<HTMLDivElement | null>;
}
function formatTime(date: Date): string {
return date.toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
});
}
function TypingIndicator() {
return (
<div className="flex justify-end">
<div className="flex max-w-[85%] items-center gap-2 rounded-2xl rounded-br-md border bg-card px-4 py-3 shadow-sm sm:max-w-[75%]">
<div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
<Bot className="size-4" aria-hidden />
</div>
<div className="flex items-center gap-1 py-1" aria-label="در حال نوشتن">
<span className="size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:0ms]" />
<span className="size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:150ms]" />
<span className="size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:300ms]" />
</div>
</div>
</div>
);
}
export function ChatMessageList({
messages,
isTyping = false,
bottomRef,
}: ChatMessageListProps) {
return (
<ScrollArea className="min-h-0 flex-1 pe-1">
<div className="flex flex-col gap-4 p-4 sm:p-5">
{messages.map((message) => {
const isUser = message.role === "user";
return (
<div
key={message.id}
className={cn("flex", isUser ? "justify-start" : "justify-end")}
>
<div
className={cn(
"flex max-w-[92%] gap-2.5 sm:max-w-[80%]",
isUser ? "flex-row" : "flex-row-reverse"
)}
>
<div
className={cn(
"flex size-7 shrink-0 items-center justify-center rounded-full sm:size-8",
isUser
? "bg-primary/10 text-primary"
: "bg-muted text-muted-foreground"
)}
>
{isUser ? (
<User className="size-4" aria-hidden />
) : (
<Bot className="size-4" aria-hidden />
)}
</div>
<div className={cn("min-w-0", typeSpacing.chatMeta)}>
<div
className={cn(
"rounded-2xl px-4 py-3 shadow-sm",
type.chat,
isUser
? "rounded-tr-md bg-primary text-primary-foreground"
: "rounded-bl-md border bg-card text-card-foreground"
)}
>
<p className="whitespace-pre-wrap break-words">{message.content}</p>
</div>
<p
className={cn(
type.caption,
isUser ? "text-start" : "text-end"
)}
>
{isUser ? "شما" : "دستیار"} · {formatTime(message.timestamp)}
</p>
</div>
</div>
</div>
);
})}
{isTyping && <TypingIndicator />}
<div ref={bottomRef} className="h-px shrink-0" aria-hidden />
</div>
</ScrollArea>
);
}