snapshot of https://github.com/shadcn-ui/ui.git (main, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
227
apps/v4/examples/aria/message-scroller-group-chat.tsx
Normal file
227
apps/v4/examples/aria/message-scroller-group-chat.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { RotateCwIcon } from "lucide-react"
|
||||
|
||||
import { Bubble, BubbleContent } from "@/styles/aria-rhea/ui/bubble"
|
||||
import { Button } from "@/styles/aria-rhea/ui/button"
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/styles/aria-rhea/ui/card"
|
||||
import { Marker, MarkerContent } from "@/styles/aria-rhea/ui/marker"
|
||||
import {
|
||||
Message,
|
||||
MessageContent,
|
||||
MessageHeader,
|
||||
} from "@/styles/aria-rhea/ui/message"
|
||||
import {
|
||||
MessageScroller,
|
||||
MessageScrollerButton,
|
||||
MessageScrollerContent,
|
||||
MessageScrollerItem,
|
||||
MessageScrollerProvider,
|
||||
MessageScrollerViewport,
|
||||
} from "@/styles/aria-rhea/ui/message-scroller"
|
||||
import { Tooltip, TooltipTrigger } from "@/styles/aria-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>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label="Reset conversation"
|
||||
isDisabled={rockyTurn === "idle"}
|
||||
onPress={() => {
|
||||
setRockyTurn("idle")
|
||||
setDemoKey((key) => key + 1)
|
||||
}}
|
||||
>
|
||||
<RotateCwIcon />
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<p>Reset</p>
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</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"
|
||||
isDisabled={isComplete}
|
||||
onPress={() =>
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user