process/stages.ex
Stages
A stage rule with numbered stages and the current one marked, for how-we-work sections.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.process_stages animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
stages |
list | [%{title: "Land", detail: "Pick one workflow"}, %{title: "Install", detail: "Make it work"}, ...] |
Two to five `%{title, detail}`. Keep details under 18 characters. |
current |
integer | 1 |
Index of the stage you are in. |
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/process/stages.ex
defmodule EHVisuals.Process.Stages do
@moduledoc """
The stage rule of an engagement: Land, Install, Enable, Expand, with the
current stage marked.
"""
use EHVisuals.Composition,
name: "process/stages",
title: "Stages",
description:
"A stage rule with numbered stages and the current one marked, for how-we-work sections."
@stages [
%{title: "Land", detail: "Pick one workflow"},
%{title: "Install", detail: "Make it work"},
%{title: "Enable", detail: "Make it yours"},
%{title: "Expand", detail: "Grow what works"}
]
attr(:stages, :list,
default: @stages,
doc: "Two to five `%{title, detail}`. Keep details under 18 characters."
)
attr(:current, :integer, default: 1, doc: "Index of the stage you are in.")
attr(:label, :string, default: "Four engagement stages with the second one current")
frame_attrs()
@spec process_stages(map()) :: Phoenix.LiveView.Rendered.t()
def process_stages(assigns) do
count = max(length(assigns.stages), 1)
column = 400 / count
items =
for {stage, index} <- Enum.with_index(assigns.stages) do
stage
|> Map.put(:index, index)
|> Map.put(:x, Float.round(20 + index * column, 1))
|> Map.put(
:number,
index |> Kernel.+(1) |> Integer.to_string() |> String.pad_leading(2, "0")
)
|> Map.put(:state, state(index, assigns.current))
end
assigns =
assigns
|> assign(:items, items)
|> assign(:progress_x, Float.round(20 + min(assigns.current, count - 1) * column, 1))
~H"""
<.frame name="process/stages" label={@label} {frame_opts(assigns)}>
<svg viewBox="0 0 440 124">
<g class="ehv-h-lift">
<line class="ehv-line ehv-a-draw" x1="20" x2="420" y1="25" y2="25" pathLength="1" />
<line
class="ehv-line-lime ehv-a-draw ehv-i-2"
x1="20"
x2={@progress_x}
y1="25"
y2="25"
pathLength="1"
/>
<g :for={item <- @items}>
<rect
:if={item.state == :now}
class="ehv-fill-lime ehv-a-ping"
x={item.x}
y="19"
width="12"
height="12"
rx="3.5"
/>
<rect
class={[marker(item.state), "ehv-a-pop ehv-i-#{item.index + 2}"]}
x={item.x}
y="19"
width="12"
height="12"
rx="3.5"
/>
<g class={"ehv-a-rise ehv-i-#{item.index + 3}"}>
<text
class={["ehv-mono", item.state == :now && "ehv-t-lime"]}
x={item.x}
y="58"
font-size="9"
>
{item.number}
</text>
<text
class={["ehv-t-strong", item.state == :next && "ehv-t-3"]}
x={item.x}
y="82"
font-size="15"
>
{item.title}
</text>
<text
class={if item.state == :next, do: "ehv-t-4", else: "ehv-t-3"}
x={item.x}
y="101"
font-size="11"
>
{item.detail}
</text>
</g>
</g>
</g>
</svg>
</.frame>
"""
end
defp state(index, current) when index < current, do: :done
defp state(index, current) when index == current, do: :now
defp state(_index, _current), do: :next
defp marker(:done), do: "ehv-fill-ink"
defp marker(:now), do: "ehv-fill-lime"
defp marker(:next), do: "ehv-node-soft"
end
assets/css/process/stages.css
/* process/stages */
.ehv-process-stages { --ehv-max-w: 560px; --ehv-step: 120ms; --ehv-iso-scale: 0.82; }