import type { Meta, StoryObj } from '@storybook/nextjs'; import * as React from 'react'; import { useStreamTokenArray } from './useStreamTokenArray'; /** * Demo component that showcases the useStreamTokenArray hook */ const StreamTokenArrayDemo = ({ tokens, isStreamFinished, ...hookProps }: { tokens: string[]; isStreamFinished: boolean; targetBufferTokens?: number; minChunkTokens?: number; maxChunkTokens?: number; frameLookBackMs?: number; adjustPercentage?: number; flushImmediatelyOnComplete?: boolean; tokenSeparator?: string; }) => { const [currentTokens, setCurrentTokens] = React.useState([]); const [finished, setFinished] = React.useState(false); const timeoutRef = React.useRef(null); const autoStream = true; const streamDelay = 300; // Auto-streaming simulation React.useEffect(() => { if (!autoStream) return; setCurrentTokens([]); setFinished(false); let index = 0; const streamNextToken = () => { if (index < tokens.length) { setCurrentTokens((prev) => [...prev, tokens[index]]); index++; timeoutRef.current = setTimeout(streamNextToken, streamDelay); } else { setFinished(true); } }; timeoutRef.current = setTimeout(streamNextToken, streamDelay); return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, [tokens, autoStream, streamDelay]); const { throttledTokens, throttledContent, isDone, flushNow, reset } = useStreamTokenArray({ tokens: (autoStream ? currentTokens : tokens).map((t) => ({ token: t, delayMs: 0 })), isStreamFinished: autoStream ? finished : isStreamFinished, ...hookProps }); return (

Stream Token Array Demo

{/* Controls */}
{/* Status */}
Total Tokens:{' '} {autoStream ? currentTokens.length : tokens.length}
Visible Tokens: {throttledTokens.length}
Stream Finished:{' '} {autoStream ? (finished ? 'Yes' : 'No') : isStreamFinished ? 'Yes' : 'No'}
Is Done: {isDone ? 'Yes' : 'No'}
{/* Progress Bar */}
Progress {throttledTokens.length} / {autoStream ? currentTokens.length : tokens.length}
{/* Token Array Visualization */}

Token Array:

{(autoStream ? currentTokens : tokens).map((token, index) => ( {token} ))}
{/* Content Output */}

Rendered Content:

{throttledContent || No content yet...}
); }; const meta: Meta = { title: 'UI/streaming/useStreamTokenArray', component: StreamTokenArrayDemo, parameters: { layout: 'fullscreen', docs: { description: { component: ` The \`useStreamTokenArray\` hook provides progressive revelation of token arrays with configurable pacing. Unlike character-based streaming, this hook works with discrete tokens (words, sentences, etc.) and reveals them in chunks with smooth animation frame-based timing. ## Features - **Token-based streaming**: Works with arrays of discrete tokens - **Configurable pacing**: Control timing, chunk sizes, and buffering - **Dual output**: Returns both token array and joined string - **Performance optimized**: Uses RAF loop and refs to minimize re-renders - **Auto-flush**: Optionally flush remaining tokens when stream finishes ## Use Cases - Word-by-word text revelation - Sentence-by-sentence content display - Progressive list building - Chat message token streaming - Any scenario requiring discrete unit streaming ` } } } }; export default meta; type Story = StoryObj; /** * Basic word-by-word streaming example with default settings. */ export const Default: Story = { args: { tokens: [ 'Hello', 'world!', 'This', 'is', 'a', 'demonstration', 'of', 'the', 'useStreamTokenArray', 'hook', 'streaming', 'tokens', 'progressively.', 'Each', 'word', 'appears', 'with', 'controlled', 'timing', 'and', 'pacing.' ], isStreamFinished: false } }; /** * Fast streaming with small chunks for rapid token revelation. */ export const FastStreaming: Story = { args: { tokens: [ 'Quick', 'fast', 'streaming', 'with', 'minimal', 'delay', 'between', 'tokens', 'for', 'a', 'more', 'rapid', 'revelation', 'effect.' ], isStreamFinished: false, targetBufferTokens: 1, minChunkTokens: 1, maxChunkTokens: 2, frameLookBackMs: 50 } }; /** * Sentence-by-sentence streaming with periods as separators. */ export const SentenceStreaming: Story = { args: { tokens: [ 'This is the first sentence.', 'Here comes the second sentence with more content.', 'The third sentence demonstrates longer text streaming.', 'Finally, this is the last sentence in our example.' ], isStreamFinished: false, targetBufferTokens: 0, minChunkTokens: 1, maxChunkTokens: 1, frameLookBackMs: 150, tokenSeparator: '\n\n' } }; /** * Manual control without auto-streaming for testing. */ export const ManualControl: Story = { args: { tokens: [ 'This', 'example', 'does', 'not', 'auto-stream.', 'Use', 'the', 'controls', 'to', 'test', 'flush', 'and', 'reset', 'functionality.' ], isStreamFinished: true } };