events/stream.ex
Event stream
A live event log in mono records, newest on top.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.events_stream animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
title |
string | "Event log · EHOS" |
|
status |
string | "Live" |
|
events |
list | [%{name: "invoice.posted", time: "09:41:07", ref: "INV-8842"}, %{name: "invoice.approved", ...}, ...] |
Up to six `%{time, name, ref}`, newest first. |
Frame props, shared by every composition
| Prop | Type | Default | What it does |
|---|---|---|---|
animated |
boolean | false |
Turns motion on. Without it the composition renders in its final, still state. |
trigger |
"mount" | "in-view" | "in-view-repeat" | "in-view" |
When the entrance plays: on page load, once when scrolled into view, or every time it comes back into view. |
hover |
boolean | false |
Enables the hover micro-interactions the composition supports. |
glow |
boolean | true |
The warm incandescent halo behind the composition. |
fade_out |
boolean | false |
Fades the bottom edge into the surface underneath. |
isometric |
boolean | false |
Tilts the composition into the isometric plane of the platform stack. |
theme |
"dark" | "light" | "dark" |
`dark` for the site canvas, `light` for the cream reversed surface. |
class |
string | nil |
Extra classes for the frame. |
rest |
global | — |
Any other HTML attribute, set on the frame. |
Source
Two files. Copy them into your app if you would rather own the code than depend on the library.
lib/eh_visuals/events/stream.ex
defmodule EHVisuals.Events.Stream do
@moduledoc """
A live event log: what happened, when, and to which record.
"""
use EHVisuals.Composition,
name: "events/stream",
title: "Event stream",
description: "A live event log in mono records, newest on top."
@events [
%{time: "09:41:07", name: "invoice.posted", ref: "INV-8842"},
%{time: "09:41:02", name: "invoice.approved", ref: "INV-8842"},
%{time: "09:40:48", name: "order.shipped", ref: "PO-2291"},
%{time: "09:39:15", name: "stock.reserved", ref: "PO-2291"},
%{time: "09:38:51", name: "order.received", ref: "PO-2291"}
]
attr(:title, :string, default: "Event log · EHOS")
attr(:status, :string, default: "Live")
attr(:events, :list, default: @events, doc: "Up to six `%{time, name, ref}`, newest first.")
attr(:label, :string, default: "A live log of business events, newest on top")
frame_attrs()
@spec events_stream(map()) :: Phoenix.LiveView.Rendered.t()
def events_stream(assigns) do
rows = assigns.events |> Enum.take(6) |> Enum.with_index()
assigns =
assigns
|> assign(:rows, rows)
|> assign(:height, 46 + length(rows) * 34 + 6)
~H"""
<.frame name="events/stream" label={@label} {frame_opts(assigns)}>
<svg viewBox={"0 0 380 #{@height}"}>
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="379" height={@height - 1} rx="12" />
<circle class="ehv-fill-lime ehv-a-ping" cx="24" cy="23" r="3.5" />
<circle class="ehv-fill-lime ehv-a-pulse" cx="24" cy="23" r="3.5" />
<text class="ehv-mono ehv-t-2 ehv-a-fade ehv-i-1" x="38" y="26.5" font-size="9.5">
{String.upcase(@title)}
</text>
<text
class="ehv-mono ehv-t-lime ehv-a-fade ehv-i-1"
x="360"
y="26.5"
font-size="9"
text-anchor="end"
>
{String.upcase(@status)}
</text>
<line class="ehv-line-soft" x1="0.5" x2="379.5" y1="46.5" y2="46.5" />
<g :for={{event, index} <- @rows} class={"ehv-a-slide ehv-i-#{index + 2}"}>
<rect
:if={index == 0}
class="ehv-fill-lime-soft"
x="8"
y={52 + index * 34}
width="364"
height="26"
rx="6"
/>
<line
:if={index > 1}
class="ehv-line-faint"
x1="20"
x2="360"
y1={48.5 + index * 34}
y2={48.5 + index * 34}
/>
<text class="ehv-mono ehv-t-4" x="20" y={69 + index * 34} font-size="9.5">
{event.time}
</text>
<text
class={["ehv-mono", if(index == 0, do: "ehv-t-lime", else: "ehv-t-ink")]}
x="98"
y={69 + index * 34}
font-size="10"
>
{String.upcase(event.name)}
</text>
<text class="ehv-mono" x="360" y={69 + index * 34} font-size="9.5" text-anchor="end">
{event.ref}
</text>
</g>
</g>
</svg>
</.frame>
"""
end
end
assets/css/events/stream.css
/* events/stream */
.ehv-events-stream { --ehv-max-w: 400px; --ehv-step: 110ms; }