Componentes

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