zeroUI

Feed

An event feed for React, the way a game reports what just happened: bands that slide in at the edge of the screen and fold away.

Install

terminal
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/feed

Examples

Events coming in

  1. Rook picked up two power cells
  2. Sable-9 delivered a sealed crate
feed-events.tsx
import { useState } from "react";import { Button } from "@/components/zero/button";import { Feed, FeedActor, FeedItem, FeedTarget } from "@/components/zero/feed";type Event = {  id: number;  tone: "default" | "accent" | "danger" | "legendary";  who: string;  did: string;  what: string;  stays?: boolean;};const more: Omit<Event, "id">[] = [  { tone: "accent", who: "You", did: "delivered", what: "a sealed crate" },  { tone: "danger", who: "Patrol", did: "spotted", what: "Juniper" },  { tone: "legendary", who: "Nightjar", did: "found", what: "the gate key" },  { tone: "default", who: "Tallow", did: "joined", what: "the shift" },];export default function FeedEvents() {  const [events, setEvents] = useState<Event[]>([    {      id: 1,      tone: "default",      who: "Rook",      did: "picked up",      what: "two power cells",      stays: true,    },    {      id: 2,      tone: "accent",      who: "Sable-9",      did: "delivered",      what: "a sealed crate",      stays: true,    },  ]);  const [next, setNext] = useState(3);  return (    <div className="grid w-full max-w-md justify-items-end gap-5">      <Feed aria-label="Events" className="min-h-40 content-end">        {events.map((e) => (          <FeedItem            key={e.id}            tone={e.tone}            duration={e.stays ? 0 : 5000}            onExpire={() =>              setEvents((list) => list.filter((x) => x.id !== e.id))            }          >            <FeedActor>{e.who}</FeedActor> {e.did}{" "}            <FeedTarget>{e.what}</FeedTarget>          </FeedItem>        ))}      </Feed>      <Button        size="sm"        variant="secondary"        onClick={() => {          setEvents((list) => [            ...list.slice(-4),            { id: next, ...more[next % more.length] },          ]);          setNext((n) => n + 1);        }}      >        Something happens      </Button>    </div>  );}

Behaviour

Each event is a dark band fading out to the left with an edge in its tone, sliding in from the right; the newest is at the bottom. After duration it calls onExpire: drop it from your list and it folds away while the rest close up.

The list is a polite live region, so each new event is read out once. FeedActor sets who did it in the display face and the tone; FeedTarget what it was done to. Under reduced motion they appear and go without sliding.

Props

FeedItem

PropTypeDefaultDescription
tone"default" | "accent" | "danger" | "rare" | "legendary""default"Its edge, and the actor's colour.
durationnumber5000Milliseconds before onExpire; 0 keeps it.
onExpire() => void—Time to drop it.

Feed, FeedActor, FeedTarget