Multi-Source Queue
A multi-source queue React component where requests from email, phone, forms and walk-ins fly from their source icons into one unified queue along an arc, each keeping a source badge, with multi-select source filtering plus autoplay and pause.
Gather requests from several channels into one queue. Along the top sit 3–6 source pills (icon, name, running count); below is the queue list. Whenever a new item arrives — a new id in the controlled items, or the next entry of script during autoPlay — a small dot launches from the matching source pill and flies along a quadratic Bézier curve to the top of the queue. On arrival the new row is inserted, existing rows are pushed down with a spring, and the new row flashes the accent color before fading. Each row keeps a source badge, title, customer, relative time and status; tap source pills to filter (multi-select), and the oldest row fades out once maxItems is exceeded.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add multi-source-queue.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<MultiSourceQueue/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/multi-source-queue.en.jsonOr, once registries are configured in components.json, install it as @webberui/multi-source-queue.
Usage
import { Mail, Phone } from "lucide-react";
import { MultiSourceQueue } from "@/components/ui/multi-source-queue";
const sources = [
{ id: "email", label: "Email", icon: <Mail />, color: "#3b82f6" },
{ id: "phone", label: "Phone", icon: <Phone />, color: "#f59e0b" },
];
// items are oldest first; pushing a new item flies it from its source pill to the top
<MultiSourceQueue
sources={sources}
items={[
{ id: "r1", sourceId: "email", title: "Washing machine leaking", customer: "Ms. Wang", status: "Open" },
]}
maxItems={8}
onFilterChange={(filter) => console.log(filter)}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
sources | MultiSourceQueueSource[] | — | Source list; 3–6 works best |
items | MultiSourceQueueItem[] | — | Queue items (oldest first); newly added ids fly from their source pill to the top of the queue, removed ids leave the queue |
filter | string[] | — | Controlled source filter (array of source ids; empty shows everything) |
defaultFilter | string[] | [] | Initial filter in uncontrolled mode |
onFilterChange | (filter: string[]) => void | — | Callback when the filter changes |
autoPlay | boolean | false | Automatically add items from script one by one (with a pause button) |
interval | number | 1400 | Autoplay interval (ms) |
script | MultiSourceQueueItem[] | — | Items added in order during autoplay; loops when it reaches the end |
maxItems | number | 8 | Maximum rows kept in the queue; the oldest row fades out beyond it |
accent | string | — | Accent color (new-row flash and focus ring); falls back to the --wb-multi-source-accent variable |
onItemArrive | (item: MultiSourceQueueItem) => void | — | Callback when an item arrives (flight finished, row inserted) |
className | string | — | Passed through to the outermost container |
MultiSourceQueueSource
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Source id, matched by each item's sourceId |
label | string | — | Source name, shown on the source pill and in screen reader announcements |
icon | ReactNode | dot icon | Source icon, ideally a lucide-react icon |
color | string | built-in palette | Source color (any CSS color) |
MultiSourceQueueItem
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | Unique id |
sourceId | string | — | Id of the source it came from |
title | string | — | Request title (e.g. "AC not cooling") |
customer | string | — | Customer name |
time | string | from arrival time | Display time text; when omitted shows "just now" / "3 min ago" |
status | string | — | Status text (e.g. "Open") |
How it works
- Flight path: on launch the component measures the source pill and the queue with
getBoundingClientRect, converts them to container-relative coordinates, and flies the dot along a quadratic Bézier (control point below the source at the target height), scaling 0.6→1 with ease[0.22, 1, 0.36, 1]. Easing is applied to the overall progress before sampling the curve, so the speed never stutters between segments. - Timing and color are CSS variables:
--wb-multi-source-fly(default520ms) sets the flight duration and--wb-multi-source-accent(default#3b82f6) sets the new-row flash and focus ring; theaccentprop writes the latter. - When a row is inserted, existing rows slide down via a layout animation (spring stiffness 380, damping 32) and the new row flashes an 8% accent background for 600ms; the source pill squeezes 1→0.92→1 and its count goes up by one.
- The controlled
itemsarray is treated as an oldest-first log: new ids fly in and ids that disappear are removed from the queue; items added byautoPlayare managed internally. - Autoplay pauses automatically while the component is offscreen (IntersectionObserver) and resumes when it scrolls back into view.
Accessibility
- The queue is a
role="list"witharia-live="polite"; new items are announced as "From Phone: AC not cooling" (the source is prepended to the title as visually hidden text) - Source pills are toggle buttons with
aria-pressed, and theiraria-labelincludes the source name and count; the row is arole="group"labelled "Filter by source" - During autoplay a Pause / Resume button (
aria-pressed) is provided, and a Clear filter button appears while filtering; every button has a focus-visible ring - The flying dot and source badge icons are
aria-hiddenso they stay out of the reading order - When the user has reduce motion enabled, there is no flight, no squeeze and no layout shift — new rows simply fade in
Issue Cascade Board
A dark, ambient-glow issue list where cards cascade in, status pills re-layout on change and edges shimmer on hover — built for product launch sections.
Drilldown Stat Grid
A grid of KPI tiles; clicking a tile expands a full-width detail panel below its row while the remaining tiles move down to make room, and clicking again collapses it.