Active Foundation · Example
Scrolling and reading the position with the three hooks
Scrolling and reading the position with the three hooksClient
No message counted as in view yet. More above: no. More below: no.
Full source examples/ui/message-scroller-hooks.tsx
// apps/docs/examples/ui/message-scroller-hooks.tsx'use client';
import { Bubble, BubbleContent,} from '@workspace/active-ui/components/primitives/bubble';import {Button} from '@workspace/active-ui/components/primitives/button';import { MessageScroller, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility,} from '@workspace/active-ui/components/primitives/message-scroller';
const MESSAGES = Array.from({length: 20}, (_, index) => ({ id: `m${index + 1}`, text: `Status update ${index + 1}`,}));
/** * The three hooks, inside the provider: buttons that scroll with * `useMessageScroller`, and a line that reads `useMessageScrollerScrollable` * (is there more above or below?) and `useMessageScrollerVisibility` (which * messages are in view). Hooks outside `MessageScrollerProvider` throw. */function Controls() { const {scrollToStart, scrollToEnd, scrollToMessage} = useMessageScroller(); const {start, end} = useMessageScrollerScrollable(); const {visibleMessageIds} = useMessageScrollerVisibility(); const first = visibleMessageIds[0]?.slice(1); const last = visibleMessageIds.at(-1)?.slice(1); return ( <div className="flex flex-col gap-2"> <div className="flex flex-wrap gap-2"> <Button variant="outline" size="sm" disabled={!start} onClick={() => scrollToStart({behavior: 'smooth'})} > First message </Button> <Button variant="outline" size="sm" onClick={() => scrollToMessage('m10', {align: 'center'})} > Message 10 </Button> <Button variant="outline" size="sm" disabled={!end} onClick={() => scrollToEnd({behavior: 'smooth'})} > Newest message </Button> </div> <p className="text-muted-foreground text-sm" data-message-scroller-state> {first && last ? `Messages ${first} to ${last} of ${MESSAGES.length} counted as in view.` : 'No message counted as in view yet.'}{' '} More above: {start ? 'yes' : 'no'}. More below: {end ? 'yes' : 'no'}. </p> </div> );}
export default function MessageScrollerHooksExample() { return ( <div className="flex w-full max-w-md flex-col gap-3"> <MessageScrollerProvider defaultScrollPosition="start"> <Controls /> <MessageScroller className="h-60 rounded-lg border"> <MessageScrollerViewport aria-label="Status updates"> <MessageScrollerContent className="gap-3 p-4"> {MESSAGES.map((message) => ( <MessageScrollerItem key={message.id} messageId={message.id}> <Bubble variant="muted" align="start"> <BubbleContent>{message.text}</BubbleContent> </Bubble> </MessageScrollerItem> ))} </MessageScrollerContent> </MessageScrollerViewport> </MessageScroller> </MessageScrollerProvider> </div> );}