چتِ پشتیبانیِ زنده
پیشنمایش تمامصفحهویجتِ گفتوگو با پشتیبان: سربرگِ تیره با عکس و وضعیتِ آنلاین، پیامها با ساعت، پاسخهای آماده، در حالِ نوشتن و ورودیِ پیام با پیوست.
۱. نصب کامپوننتها
این بلاک از ۶ آیتم دیگ استفاده میکند: avatarbuttonchat-messagecontrol-sizeiconsprompt-input
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chat-message.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/prompt-input.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chat-message.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/prompt-input.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chat-message.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/prompt-input.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/chat-message.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/prompt-input.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
live-chat.tsx
"use client"
import * as React from "react"
import { XIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { ChatMessage, ChatMessageAvatar, ChatMessageBubble, ChatMessageContent } from "@/components/ui/chat-message"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { PromptInput, PromptInputSubmitButton, PromptInputTextarea, PromptInputToolbar, PromptInputTools, PromptInputAttachButton } from "@/components/ui/prompt-input"
import { stockImages } from "@/lib/stock-images"
type Message = { id: number; role: "user" | "assistant"; text: string; time: string }
const INITIAL: Message[] = [
{ id: 1, role: "assistant", text: "سلام، من امیرم از پشتیبانی آوند. چطور کمکت کنم؟", time: "۱۰:۱۲" },
{ id: 2, role: "user", text: "فاکتوری که دیروز فرستادم برای مشتری پیامک نشده.", time: "۱۰:۱۳" },
{ id: 3, role: "assistant", text: "بررسی کردم؛ شمارهٔ مشتری یک رقم کم داشت. اصلاحش کردم و دوباره فرستادم.", time: "۱۰:۱۵" },
]
const QUICK = ["ممنون، حل شد", "یک سؤالِ دیگر دارم", "میخواهم تماس بگیرید"]
/**
* چتِ پشتیبانی زنده (ویجت): سربرگِ تیره با عکسِ پشتیبان، وضعیتِ آنلاین و زمانِ پاسخ؛
* پیامها با ساعت، پاسخهای آماده بهصورتِ دکمه و ورودی پیام با پیوست. پاسخِ نمونه
* بعد از «در حالِ نوشتن» میآید.
*/
export default function LiveChatBlock() {
const [messages, setMessages] = React.useState(INITIAL)
const [typing, setTyping] = React.useState(false)
const endRef = React.useRef<HTMLDivElement>(null)
React.useEffect(() => {
endRef.current?.scrollIntoView({ block: "nearest" })
}, [messages, typing])
function send(text: string) {
if (!text.trim()) return
setMessages((list) => [...list, { id: list.length + 1, role: "user", text, time: "۱۰:۱۶" }])
setTyping(true)
window.setTimeout(() => {
setTyping(false)
setMessages((list) => [...list, { id: list.length + 1, role: "assistant", text: "خواهش میکنم! اگر کارِ دیگری داشتی همینجا بنویس.", time: "۱۰:۱۶" }])
}, 1200)
}
return (
<ControlSizeProvider size="xl" asChild>
<div className="flex min-h-svh items-end justify-center bg-subtle p-4 sm:items-center">
<h1 className="sr-only">گفتوگو با پشتیبانی</h1>
<section aria-label="گفتوگو با پشتیبانی" className="flex h-[40rem] w-full max-w-md flex-col overflow-hidden rounded-xl border border-border bg-card shadow-card">
{/* سربرگِ برند با گرادیانِ ملایم، نه سیاه. */}
<header className="flex items-center gap-3 bg-[linear-gradient(135deg,var(--dig-violet-700),var(--dig-indigo-700))] p-4 text-[var(--dig-white)]">
<span className="relative">
<Avatar alt="امیر رضایی" src={stockImages.personMan1.src} />
<span aria-hidden className="absolute -end-0.5 -bottom-0.5 size-3 rounded-full bg-[var(--dig-green-400)] ring-2 ring-[var(--dig-violet-700)]" />
</span>
<span className="flex flex-1 flex-col">
<span className="font-semibold">امیر رضایی</span>
<span className="text-xs text-[var(--dig-violet-100)]">آنلاین · معمولاً زیرِ ۵ دقیقه جواب میدهد</span>
</span>
<Button variant="ghost" size="icon" aria-label="بستنِ گفتوگو" className="text-[var(--dig-white)] hover:bg-[color-mix(in_oklab,var(--dig-white)_15%,var(--dig-violet-700))]">
<XIcon />
</Button>
</header>
<div className="flex flex-1 flex-col gap-4 overflow-y-auto p-4" aria-live="polite">
<span className="self-center rounded-full bg-subtle px-3 py-1 text-xs text-muted-foreground">امروز</span>
{messages.map((message) => (
<ChatMessage key={message.id} role={message.role}>
{message.role === "assistant" && <ChatMessageAvatar alt="امیر رضایی" src={stockImages.personMan1.src} />}
<ChatMessageContent>
<ChatMessageBubble>{message.text}</ChatMessageBubble>
<span className="px-1 text-xs text-muted-foreground">{message.time}</span>
</ChatMessageContent>
</ChatMessage>
))}
{typing && <span className="text-sm text-muted-foreground">امیر در حالِ نوشتن است…</span>}
<div ref={endRef} />
</div>
<div className="flex flex-col gap-3 border-t border-border p-4">
<div className="flex flex-wrap gap-2" aria-label="پاسخهای آماده">
{QUICK.map((item) => (
<Button key={item} variant="outline" onClick={() => send(item)} disabled={typing}>
{item}
</Button>
))}
</div>
<PromptInput onSubmit={send} disabled={typing}>
<PromptInputTextarea placeholder="پیامت را بنویس…" aria-label="پیام" />
<PromptInputToolbar>
<PromptInputTools>
<PromptInputAttachButton />
</PromptInputTools>
<PromptInputSubmitButton loading={typing} />
</PromptInputToolbar>
</PromptInput>
</div>
</section>
</div>
</ControlSizeProvider>
)
}