connections/sync.ex
Sync
Two systems that hold the same record, with changes travelling both ways.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.connections_sync animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
left |
map | %{title: "Stock list", kind: "Spreadsheet"} |
`%{kind, title}` of the left card. |
right |
map | %{title: "Inventory", kind: "ERP"} |
`%{kind, title}` of the right card. |
status |
string | "In sync" |
|
stamp |
string | "Last change 09:41:07" |
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/connections/sync.ex
defmodule EHVisuals.Connections.Sync do
@moduledoc """
Two systems that hold the same record and keep it in step.
"""
use EHVisuals.Composition,
name: "connections/sync",
title: "Sync",
description: "Two systems that hold the same record, with changes travelling both ways."
attr(:left, :map,
default: %{kind: "Spreadsheet", title: "Stock list"},
doc: "`%{kind, title}` of the left card."
)
attr(:right, :map,
default: %{kind: "ERP", title: "Inventory"},
doc: "`%{kind, title}` of the right card."
)
attr(:status, :string, default: "In sync")
attr(:stamp, :string, default: "Last change 09:41:07")
attr(:label, :string, default: "A spreadsheet and an ERP keeping the same record in sync")
frame_attrs()
@spec connections_sync(map()) :: Phoenix.LiveView.Rendered.t()
def connections_sync(assigns) do
assigns =
assigns
|> assign(:cards, [{assigns.left, 10, 1}, {assigns.right, 270, 3}])
|> assign(:pill_w, 26 + String.length(assigns.status) * 6.2)
~H"""
<.frame name="connections/sync" label={@label} {frame_opts(assigns)}>
<svg viewBox="0 0 420 160">
<g class="ehv-h-lift">
<g :for={{card, x, step} <- @cards} class={"ehv-a-rise ehv-i-#{step}"}>
<rect class="ehv-card" x={x + 0.5} y="20.5" width="139" height="99" rx="10" />
<text class="ehv-mono" x={x + 14} y="43" font-size="8">{String.upcase(card.kind)}</text>
<text class="ehv-t-strong" x={x + 14} y="64" font-size="13.5">{card.title}</text>
<rect class="ehv-fill-lime-soft" x={x + 8} y="76" width="124" height="16" rx="4" />
<rect class="ehv-fill-lime" x={x + 14} y="81.5" width="70" height="5" rx="2.5" />
<rect class="ehv-fill-soft" x={x + 14} y="101" width="92" height="5" rx="2.5" />
</g>
<g class="ehv-a-fade ehv-i-4">
<line class="ehv-line-ink" x1="158" x2="254" y1="48" y2="48" />
<path class="ehv-fill-2" d="M254 43.5 l8 4.5 l-8 4.5 z" />
<line class="ehv-line-ink" x1="166" x2="262" y1="92" y2="92" />
<path class="ehv-fill-2" d="M166 87.5 l-8 4.5 l8 4.5 z" />
</g>
<g :if={@animated} class="ehv-smil ehv-a-fade ehv-i-8">
<circle class="ehv-fill-lime" cy="48" r="2.5">
<animate attributeName="cx" from="158" to="258" dur="2s" repeatCount="indefinite" />
<animate
attributeName="opacity"
values="0;1;1;0"
keyTimes="0;0.2;0.8;1"
dur="2s"
repeatCount="indefinite"
/>
</circle>
<circle class="ehv-fill-lime" cy="92" r="2.5">
<animate
attributeName="cx"
from="262"
to="162"
dur="2s"
begin="1s"
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
values="0;1;1;0"
keyTimes="0;0.2;0.8;1"
dur="2s"
begin="1s"
repeatCount="indefinite"
/>
</circle>
</g>
<g class="ehv-a-pop ehv-i-6">
<rect class="ehv-well" x={210 - @pill_w / 2} y="60" width={@pill_w} height="20" rx="6" />
<circle class="ehv-fill-lime ehv-a-pulse" cx={210 - @pill_w / 2 + 11} cy="70" r="2.5" />
<text
class="ehv-mono ehv-t-lime"
x={210 + @pill_w / 2 - 9}
y="73.5"
font-size="8.5"
text-anchor="end"
>
{String.upcase(@status)}
</text>
</g>
<text class="ehv-mono ehv-a-fade ehv-i-7" x="210" y="146" font-size="8" text-anchor="middle">
{String.upcase(@stamp)}
</text>
</g>
</svg>
</.frame>
"""
end
end
assets/css/connections/sync.css
/* connections/sync */
.ehv-connections-sync { --ehv-max-w: 500px; --ehv-iso-scale: 0.82; }