Waterfall: horizontal spans laid out on ONE shared time axis, with a ruler. No Fluxon equivalent. Pure presentational server render; no JS hook.
<.waterfall id="run-phases" ticks={@ticks} label="Phase">
<.waterfall_lane label="plan" status={:done} left={0.0} width={31.4} bar_label="4m 12s">
<:icon><.icon name="hero-check-circle" /></:icon>
</.waterfall_lane>
<.waterfall_lane label="implement" status={:danger} left={31.4} width={52.1} bar_label="7m 02s" />
<.waterfall_lane label="test" status={:pending} queued bar_label="queued" />
</.waterfall>Why this is not timeline/1
timeline/1 is a VERTICAL sequence sized by its content: a marker rail, a
connector, one row per event. A waterfall is horizontal and sized by
DURATION — the whole claim it makes is that two lanes are comparable because
their bars are measured against the same axis. Those are different layouts,
not two modes of one component, and a component that tried to be both would
have to choose which of the two its status, spacing and rail meant.
The axis is the component's contract
The caller supplies left and width as PERCENTAGES of a shared axis it
computed, and ticks as percentages on that same axis. The component does
no time math and formats no timestamps — it cannot, because only the caller
knows whether the axis is wall-clock, CPU time, or bytes. What the component
guarantees is that the ruler, the gridlines and every lane's bar are laid out
in ONE track of identical width, so those percentages mean the same thing on
every row. That is why the ticks live on the root and are drawn ONCE, as a
grid layer behind every lane, rather than passed to each lane: two lanes
cannot disagree about an axis neither of them owns.
A lane with no position on the axis (never started) is queued — rendered as
a dashed placeholder at the right edge rather than given a fabricated
left. Inventing a position for work that has not happened is the one thing
a waterfall must not do.
--lui-waterfall-label-w tunes the label column.
Summary
Functions
Attributes
id(:string) - DOM id for the root element. Defaults tonil.ticks(:list) - Shared axis ruler marks: a list of%{label: string, pct: number}, wherepctis a percentage of the same axis lanes position against. Also drawn as gridlines behind the lanes. Defaults to[].label(:string) - Heading for the label column, rendered above the lane labels. Defaults tonil.class(:any) - Extra classes merged onto the root element. Defaults tonil.- Global attributes are accepted. Arbitrary HTML/
phx-*attributes passed through.
Slots
inner_block(required) - One or morewaterfall_lane/1children.
Attributes
label(:string) - Lane name, shown in the fixed label column. Defaults tonil.at(:string) - Optional preformatted secondary line under the label; not formatted by the component. Defaults tonil.status(:atom) - Bar status, rendered as data-status for CSS coloring. Defaults to:neutral. Must be one of:done,:active,:pending,:danger,:warning, or:neutral.state_label(:string) - State word (e.g. "Failed"). Set whenever status is meaningful so state is never color-only; rendered for assistive tech. Defaults tonil.left(:float) - Bar start as a percentage of the shared axis. Defaults to0.0.width(:float) - Bar length as a percentage of the shared axis. Defaults to0.0.bar_label(:string) - Text inside (or beside) the bar, typically a preformatted duration. Defaults tonil.queued(:boolean) - Lane has no position on the axis yet. Renders a dashed placeholder at the right edge instead of a positioned bar;left/widthare ignored. Defaults tofalse.selected(:boolean) - Marks this lane as the current selection. Defaults tofalse.navigate(:string) - When set, the whole lane is a navigation link. Defaults tonil.patch(:string) - When set, the whole lane is a patch link. Defaults tonil.class(:any) - Extra classes merged onto the lane root. Defaults tonil.- Global attributes are accepted. Arbitrary HTML/
phx-*attributes passed through.
Slots
inner_block- Optional trailing content in the label column (badges, counts).icon- Optional glyph rendered before the label. A SLOT, not an icon name: the host app owns its icon set, and a shared component that dictated one would crash any caller whose vocabulary differs.