Files
upstream-shadcn-ui/apps/v4/examples/radix/message-scroller-group-chat.tsx
upstream-mirror 1416db152c
All checks were successful
Release / Create Version PR or Publish Stable Release (push) Has been skipped
Release / Publish Prerelease to NPM (push) Has been skipped
Monitor Registries / Monitor registry health (push) Has been skipped
snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
2026-09-15 22:44:43 +05:00

234 lines
6.7 KiB
TypeScript

"use client"
import * as React from "react"
import { RotateCwIcon } from "lucide-react"
import { Bubble, BubbleContent } from "@/styles/radix-rhea/ui/bubble"
import { Button } from "@/styles/radix-rhea/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/styles/radix-rhea/ui/card"
import { Marker, MarkerContent } from "@/styles/radix-rhea/ui/marker"
import {
Message,
MessageContent,
MessageHeader,
} from "@/styles/radix-rhea/ui/message"
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
} from "@/styles/radix-rhea/ui/message-scroller"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/styles/radix-rhea/ui/tooltip"
const currentUser = "Grace"
const initialItems = [
{
id: "group-1",
type: "message",
sender: "Grace",
role: "participant",
text: "@mary, the astrophage line keeps matching Venus energy output. Can you check my math?",
},
{
id: "group-2",
type: "message",
sender: "Mary (Agent)",
role: "assistant",
text: "Yes. Confirmed. The curve points to a microorganism harvesting stellar energy and breeding near carbon dioxide. If @rocky agrees, this is the clue we need.",
},
{
id: "group-3",
type: "message",
sender: "Grace",
role: "participant",
text: "ping @rocky",
scrollAnchor: true,
},
] satisfies GroupChatItem[]
const rockyMarker = {
id: "group-4",
type: "event",
text: "Rocky has joined the chat",
scrollAnchor: true,
} satisfies GroupChatItem
const rockyMessage = {
id: "group-5",
type: "message",
sender: "Rocky",
role: "participant",
text: "Amaze. Astrophage eats light, makes heat, goes to carbon dioxide. Rocky has fuel model. Grace is smart.",
} satisfies GroupChatItem
type GroupChatItem =
| {
id: string
type: "event"
text: string
scrollAnchor?: boolean
}
| {
id: string
type: "message"
sender: string
role: "assistant" | "participant"
text: string
scrollAnchor?: boolean
}
export function MessageScrollerGroupChat() {
const [demoKey, setDemoKey] = React.useState(0)
const [rockyTurn, setRockyTurn] = React.useState<
"idle" | "marker" | "message"
>("idle")
const items =
rockyTurn === "message"
? [...initialItems, rockyMarker, rockyMessage]
: rockyTurn === "marker"
? [...initialItems, rockyMarker]
: initialItems
const buttonLabel =
rockyTurn === "idle" ? "Add Rocky" : "Send Message as Rocky"
const isComplete = rockyTurn === "message"
return (
<MessageScrollerProvider>
<div className="relative flex flex-col gap-4">
<Card className="mx-auto h-140 w-full max-w-sm gap-0">
<CardHeader className="gap-1 border-b">
<CardTitle>Group Chat</CardTitle>
<CardDescription>
A group chat with several participants and an assistant. The
Marker is marked as a turn.
</CardDescription>
<CardAction>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
variant="outline"
size="icon"
aria-label="Reset conversation"
disabled={rockyTurn === "idle"}
onClick={() => {
setRockyTurn("idle")
setDemoKey((key) => key + 1)
}}
>
<RotateCwIcon />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Reset</p>
</TooltipContent>
</Tooltip>
</CardAction>
</CardHeader>
<CardContent className="min-h-0 flex-1 p-0">
<MessageScrollerProvider>
<MessageScroller key={demoKey}>
<MessageScrollerViewport>
<MessageScrollerContent className="p-(--card-spacing)">
{items.map((item) =>
item.type === "message" ? (
<GroupChatMessage key={item.id} item={item} />
) : (
<GroupChatMarker
key={item.id}
item={item}
scrollAnchor={item.scrollAnchor}
/>
)
)}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
</CardContent>
<CardFooter className="flex flex-col items-center gap-2 border-t">
<Button
type="button"
disabled={isComplete}
onClick={() =>
setRockyTurn((turn) => (turn === "idle" ? "marker" : "message"))
}
className="w-full"
variant="secondary"
>
{buttonLabel}
</Button>
<p className="text-xs text-muted-foreground">
{rockyTurn === "idle"
? "This will create a marker and make it the anchor"
: "Now send Rocky's reply into the conversation"}
</p>
</CardFooter>
</Card>
<div className="mx-auto max-w-sm px-0.5 text-center text-xs text-balance text-muted-foreground">
When a user joins, a marker is created. scrollAnchor on the marker
marks it as the next turn
</div>
</div>
</MessageScrollerProvider>
)
}
function GroupChatMessage({
item,
}: {
item: Extract<GroupChatItem, { type: "message" }>
}) {
const isCurrentUser = item.sender === currentUser
const variant = isCurrentUser
? "muted"
: item.role === "assistant"
? "ghost"
: "tinted"
return (
<MessageScrollerItem messageId={item.id} scrollAnchor={item.scrollAnchor}>
<Message align={isCurrentUser ? "end" : "start"}>
<MessageContent>
{!isCurrentUser && <MessageHeader>{item.sender}</MessageHeader>}
<Bubble variant={variant}>
<BubbleContent>{item.text}</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageScrollerItem>
)
}
function GroupChatMarker({
item,
scrollAnchor = false,
}: {
item: Extract<GroupChatItem, { type: "event" }>
scrollAnchor?: boolean
}) {
return (
<MessageScrollerItem scrollAnchor={scrollAnchor}>
<Marker variant="separator">
<MarkerContent>{item.text}</MarkerContent>
</Marker>
</MessageScrollerItem>
)
}