Dashral Pro is on the wayGet 30% off at launch

Dashral

Notification fan-outPro

One event fanning out to email, push, SMS, team chat and webhooks by each person's preferences, every channel at its own pace; a text that fails is retried in amber before it lands, channels count what they deliver, and the latest notifications stack up as they arrive. Pauses, and stacks on phones.

When to use

  • One event fanning out to email, push, SMS, chat and webhooks by each person's preferences.
  • Messaging and notification products; a live view of your own deliveries.

When not to use

  • Products that send one kind of message.
  • People choosing their own notification settings: a settings form, not the diagram.

Usage

Notification fan-out with your own data: every prop has the sample as its default, so pass only what you have.

example.tsx
import { useEffect, useRef } from "react"
import { NotificationFanout, type FanoutHandle } from "@/components/dashral/blocks/notification-fanout"

const fanout = useRef<FanoutHandle>(null)
useEffect(() => outbox.on("event", (e) => fanout.current?.send(e.name, { to: e.channels, failed: e.failed })), [])

<NotificationFanout ref={fanout} mode="live" channels={channels} />

API reference

NotificationFanout

The section: a heading, the fanout in a dotted panel with a Live badge and Pause, the latest notifications stacked under it, and a row of figures. Every part comes from props, with a sample fanout by default.

PropTypeDefault
eyebrowReact.ReactNode"Notifications"
titleReact.ReactNodesample
descriptionReact.ReactNodesample
sourcesFlowNode[]

Where events come from, and the channels they go out on (chance, pace and flaky shape the demo).

sample
channelsFlowNode[]sample
eventsNotifyEvent[]

The sample events the demo sends, one after another.

sample
hubLabelstring

The hub's name; under it, the count of messages delivered, from delivered.

"Notify"
deliverednumber77_149
stats{ value: string; label: string }[]

The figures under the diagram; none hides the row.

sample
modePipelineProps["mode"]

With your own notifications instead of sample events (see Fanout's mode="live"); sent ones join the stack.

—
refReact.Ref<FanoutHandle>—

Fanout

Events fanning out to the channels people want them on. Each event comes in as a hollow dot and is routed in the hub (its outline pulses); a solid square goes out to each channel it's for, by the share of people who want that channel, each at the channel's own pace. A message that fails first (amber on its channel) is retried and goes through. Channels light up and count what they deliver, and the page hears about every delivery. Side by side when there's room, stacked on a narrow container (the channels then show their names only). One requestAnimationFrame loop writes the SVG and the cards directly. Sample events by default; with mode="live", only the ones you send (see FanoutHandle). It stops off screen, in a hidden tab, when paused and (live) with nothing on its way; with reduced motion it's a still picture.

PropTypeDefault
inputsFlowNode[]

Required.

—
hub{ label: string; description: React.ReactNode }

Required.

—
outputsFlowNode[]

Required.

—
speednumber

How fast records flow: 1 is the usual pace, 2 twice as fast, 0 holds them still.

1
pausedboolean

Brings the flow to rest where it is (easing down, not stopping in one frame).

false
eventsNotifyEvent[]

The sample events coming in, one after another ("demo").

sample
onDeliver(delivery: Delivery) => void

Called as each message is delivered (after its retry, when it needed one), in the order they land.

—
onSent(sent: Sent) => void

Called once all of an event's messages are delivered.

—
mode"demo" | "live"

"demo" plays sample events on its own; "live" moves one only when you send it (see FanoutHandle).

"demo"
refReact.Ref<FanoutHandle>—
classNamestring—

type FlowNode

PropTypeDefault
idstring—
labelstring—
descriptionstring—
detailstring

More about it, in a tooltip on hover and keyboard focus (touch screens don't show it, so nothing essential).

—
iconLucideIcon—
markReact.ReactNode

A brand's own mark (white, on color), in place of an icon.

—
colorstring—
metric{ unit: string; value?: number; base?: number; per?: number }

A count on the card, then unit. value is your own number, shown as it is. Without it the card counts messages itself: base plus one for every per it sends or delivers.

—
chancenumber

A channel's share of people who get it (their preferences), how long it takes (1 is the usual), and how often it fails first.

—
pacenumber—
flakynumber—

type NotifyEvent

What happened, and who it's for, as an event comes in.

PropTypeDefault
titlestring—
detailstring—
tostring—

type Delivery

One message delivered.

PropTypeDefault
idnumber—
channelnumber—
eventNotifyEvent—
retriedboolean—

type Sent

One event with all its messages delivered, for the stack of notifications: which channels, and how many retried.

PropTypeDefault
idnumber—
eventNotifyEvent—
channelsnumber[]—
retriednumber—

type FanoutHandle

Moves events through a live fanout (mode="live").

PropTypeDefault
send(event: NotifyEvent, options?: { from?: string; to?: string[]; failed?: string[] }) => void

Sends one event from the source with id from (the first without it) to the channels in to (every one without it); the ones in failed fail first and are retried. onDeliver hears each message, onSent the whole event. Off screen, in a hidden tab or with reduced motion it isn't drawn, but it's counted and reported at once.

—