ruststream 0.7.5

Async messaging framework for Rust: broker-agnostic traits, router, codecs, and a conformance harness for broker authors.
Documentation
import React, {type ReactNode, useCallback, useEffect, useRef, useState} from 'react';
import {translate} from '@docusaurus/Translate';
import shared from './landing.module.css';
import styles from './compare.module.css';

export interface TerminalLine {
  kind: 'command' | 'log' | 'note' | 'output';
  text: string;
}

interface Props {
  title: string;
  lines: TerminalLine[];
}

const STEP_MS = 420;

/**
 * A terminal that prints its lines one by one once it scrolls into view, with a button that
 * plays them again. Under `prefers-reduced-motion: reduce` every line is there from the start.
 */
export default function Terminal({title, lines}: Props): ReactNode {
  const [shown, setShown] = useState(lines.length);
  const [started, setStarted] = useState(false);
  const root = useRef<HTMLDivElement>(null);
  const timer = useRef<number | undefined>(undefined);

  const play = useCallback(() => {
    window.clearInterval(timer.current);
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
      setShown(lines.length);
      return;
    }
    setShown(0);
    timer.current = window.setInterval(() => {
      setShown((count) => {
        if (count + 1 >= lines.length) window.clearInterval(timer.current);
        return Math.min(count + 1, lines.length);
      });
    }, STEP_MS);
  }, [lines.length]);

  useEffect(() => {
    const element = root.current;
    if (!element || started) return undefined;
    const observer = new IntersectionObserver(([entry]) => {
      if (entry?.isIntersecting) {
        setStarted(true);
        play();
      }
    });
    observer.observe(element);
    return () => observer.disconnect();
  }, [play, started]);

  useEffect(() => () => window.clearInterval(timer.current), []);

  return (
    <div ref={root} className={shared.window}>
      <div className={shared.windowBar}>
        <span className={shared.dots} aria-hidden="true">
          <span />
          <span />
          <span />
        </span>
        <span>{title}</span>
        <button
          type="button"
          className={styles.replay}
          onClick={play}
          aria-label={translate({
            id: 'landing.compare.replayLabel',
            message: 'Play the run again',
          })}>
          <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true">
            <path
              d="M13 8a5 5 0 1 1-1.6-3.7M13 2.5v3h-3"
              fill="none"
              stroke="currentColor"
              strokeWidth="1.6"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
          {translate({id: 'landing.compare.replay', message: 'Replay'})}
        </button>
      </div>
      <pre className={styles.terminal} aria-live="off">
        {lines.map((line, index) => (
          <span
            key={index}
            className={styles[line.kind]}
            style={{visibility: index < shown ? 'visible' : 'hidden'}}>
            {line.kind === 'command' && <span className={styles.prompt}>$ </span>}
            {line.text}
            {'\n'}
          </span>
        ))}
      </pre>
    </div>
  );
}