Message Scroller
Scroll de transcript com auto-âncora e botão para o fim (@shadcn/react).
Conteúdo e mídia
Carregando demonstração…
Código da demo
"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Bubble, BubbleContent } from "@/components/ui/bubble";
import { Message, MessageContent } from "@/components/ui/message";
import {
MessageScroller,
MessageScrollerButton,
MessageScrollerContent,
MessageScrollerItem,
MessageScrollerProvider,
MessageScrollerViewport,
useMessageScroller,
} from "@/components/ui/message-scroller";
const SEED = [
"Primeira mensagem da thread.",
"Segunda — role para ver o botão de scroll.",
"Terceira mensagem.",
"Quarta mensagem.",
"Quinta mensagem.",
"Sexta mensagem.",
"Sétima mensagem.",
"Oitava — âncora no fim.",
];
function scrollViewport(to: "start" | "end") {
const vp = document.querySelector<HTMLElement>(
"[data-slot=message-scroller-viewport]",
);
if (!vp) return;
vp.scrollTo({
top: to === "end" ? vp.scrollHeight : 0,
behavior: "auto",
});
}
function ScrollControls() {
const { scrollToEnd, scrollToStart } = useMessageScroller();
return (
<div className="flex flex-wrap gap-fe-2">
<Button
type="button"
variant="secondary"
size="sm"
onClick={() => {
scrollToEnd({ behavior: "auto" });
scrollViewport("end");
}}
>
Ir ao fim
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
scrollToStart({ behavior: "auto" });
scrollViewport("start");
}}
>
Ir ao início
</Button>
</div>
);
}
export function Demo() {
const [messages, setMessages] = React.useState(SEED);
return (
<div className="flex w-full max-w-md flex-col gap-fe-3">
<Button
type="button"
variant="outline"
size="sm"
className="self-start"
onClick={() =>
setMessages((prev) => [
...prev,
`Nova mensagem ${prev.length + 1} — ${new Date().toLocaleTimeString("pt-BR")}`,
])
}
>
Adicionar mensagem
</Button>
<MessageScrollerProvider
autoScroll={false}
defaultScrollPosition="start"
>
<ScrollControls />
<MessageScroller className="h-64 rounded-card border border-border bg-card">
<MessageScrollerViewport>
<MessageScrollerContent>
{messages.map((text, index) => (
<MessageScrollerItem
key={`${index}-${text.slice(0, 12)}`}
messageId={`msg-${index}`}
scrollAnchor={index === messages.length - 1}
>
<Message align={index % 2 === 0 ? "start" : "end"}>
<MessageContent>
<Bubble
variant={index % 2 === 0 ? "secondary" : "default"}
align={index % 2 === 0 ? "start" : "end"}
>
<BubbleContent>{text}</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
</div>
);
}- Acessibilidade
- Viewport é region focável; content role=log; botão inerte quando inativo.
- Importar
import { MessageScrollerProvider, MessageScroller, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem, MessageScrollerButton } from "@financeequity/ui/message-scroller"Neste repositório:
import { MessageScrollerProvider, MessageScroller, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem, MessageScrollerButton } from "@/components/ui/message-scroller"- Dependências
- @shadcn/react