Data Display

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1400
8
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
sourcesMultiSourceQueueSource[]—Source list; 3–6 works best
itemsMultiSourceQueueItem[]—Queue items (oldest first); newly added ids fly from their source pill to the top of the queue, removed ids leave the queue
filterstring[]—Controlled source filter (array of source ids; empty shows everything)
defaultFilterstring[][]Initial filter in uncontrolled mode
onFilterChange(filter: string[]) => void—Callback when the filter changes
autoPlaybooleanfalseAutomatically add items from script one by one (with a pause button)
intervalnumber1400Autoplay interval (ms)
scriptMultiSourceQueueItem[]—Items added in order during autoplay; loops when it reaches the end
maxItemsnumber8Maximum rows kept in the queue; the oldest row fades out beyond it
accentstring—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)
classNamestring—Passed through to the outermost container

MultiSourceQueueSource

FieldTypeDefaultDescription
idstring—Source id, matched by each item's sourceId
labelstring—Source name, shown on the source pill and in screen reader announcements
iconReactNodedot iconSource icon, ideally a lucide-react icon
colorstringbuilt-in paletteSource color (any CSS color)

MultiSourceQueueItem

FieldTypeDefaultDescription
idstring—Unique id
sourceIdstring—Id of the source it came from
titlestring—Request title (e.g. "AC not cooling")
customerstring—Customer name
timestringfrom arrival timeDisplay time text; when omitted shows "just now" / "3 min ago"
statusstring—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 (default 520ms) sets the flight duration and --wb-multi-source-accent (default #3b82f6) sets the new-row flash and focus ring; the accent prop 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 items array is treated as an oldest-first log: new ids fly in and ids that disappear are removed from the queue; items added by autoPlay are 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" with aria-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 their aria-label includes the source name and count; the row is a role="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-hidden so 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