browser/loading.ex
Browser loading
A browser window while a page loads, with a progress line under the address bar and skeleton blocks for the content.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.browser_loading animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
url |
string | "erp.yourcompany.com/orders" |
Up to 44 characters. |
progress |
integer | 62 |
How far the page has loaded, 0 to 100. |
status |
string | "Waiting for the ERP" |
The status bar line, up to 40 characters. |
rows |
integer | 3 |
Skeleton list rows under the three tiles, 1 to 5. |
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/browser/loading.ex
defmodule EHVisuals.Browser.Loading do
@moduledoc """
A browser window while a page loads: a progress line under the address bar
and skeleton blocks where the content will be.
"""
use EHVisuals.Composition,
name: "browser/loading",
title: "Browser loading",
description:
"A browser window while a page loads, with a progress line under the address bar and skeleton blocks for the content."
@row_widths [150, 118, 172, 104, 140]
attr(:url, :string, default: "erp.yourcompany.com/orders", doc: "Up to 44 characters.")
attr(:progress, :integer, default: 62, doc: "How far the page has loaded, 0 to 100.")
attr(:status, :string,
default: "Waiting for the ERP",
doc: "The status bar line, up to 40 characters."
)
attr(:rows, :integer, default: 3, doc: "Skeleton list rows under the three tiles, 1 to 5.")
attr(:label, :string,
default: "A browser window loading a page, with skeleton blocks in place of the content"
)
frame_attrs()
@spec browser_loading(map()) :: Phoenix.LiveView.Rendered.t()
def browser_loading(assigns) do
progress = assigns.progress |> max(0) |> min(100)
count = assigns.rows |> max(1) |> min(5)
status_y = 184 + count * 24 + 4
rows =
for index <- 0..(count - 1) do
%{index: index, y: 184 + index * 24, w: Enum.at(@row_widths, index)}
end
assigns =
assigns
|> assign(:percent, progress)
|> assign(:bar_w, Float.round(progress / 100 * 398, 1))
|> assign(
:tiles,
for(index <- 0..2, do: %{index: index, x: Float.round(24 + index * 121.3, 1)})
)
|> assign(:items, rows)
|> assign(:status_y, status_y)
|> assign(:height, status_y + 28)
~H"""
<.frame name="browser/loading" label={@label} {frame_opts(assigns)}>
<svg viewBox={"0 0 400 #{@height}"}>
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="399" height={@height - 1} rx="12" />
<g class="ehv-a-fade ehv-i-1">
<path class="ehv-line-ink ehv-round" d="M21 16 l-4 4 l4 4" />
<path class="ehv-line ehv-round" d="M33 16 l4 4 l-4 4" />
<path class="ehv-line-ink ehv-round" d="M49 17 l6 6 m0 -6 l-6 6" />
<rect class="ehv-well" x="68" y="9" width="314" height="22" rx="11" />
<g class="ehv-browser-loading__spin">
<circle class="ehv-line-soft" cx="82" cy="20" r="4.5" />
<path class="ehv-line-lime ehv-round" d="M82 15.5 a4.5 4.5 0 0 1 4.5 4.5" />
</g>
<text class="ehv-mono ehv-t-2" x="94" y="23" font-size="8.5">{@url}</text>
<line class="ehv-line-soft" x1="0.5" x2="399.5" y1="40.5" y2="40.5" />
</g>
<g class="ehv-a-fade ehv-i-2">
<rect
class="ehv-fill-lime ehv-a-grow-x ehv-i-2"
x="1"
y="39.5"
width={@bar_w}
height="2"
/>
<circle class="ehv-fill-lime ehv-a-pulse ehv-i-6" cx={1 + @bar_w} cy="40.5" r="2.5" />
</g>
<g class="ehv-a-fade ehv-i-3">
<rect
class="ehv-fill-soft ehv-a-pulse ehv-i-0"
x="24"
y="60"
width="56"
height="6"
rx="3"
/>
<rect
class="ehv-fill-strong ehv-a-pulse ehv-i-1"
x="24"
y="76"
width="168"
height="14"
rx="4"
/>
<rect
class="ehv-fill-soft ehv-a-pulse ehv-i-2"
x="24"
y="100"
width="236"
height="6"
rx="3"
/>
<rect class="ehv-line-soft" x="306.5" y="70.5" width="69" height="23" rx="6" />
<rect
class="ehv-fill-soft ehv-a-pulse ehv-i-2"
x="324"
y="79.5"
width="34"
height="5"
rx="2.5"
/>
</g>
<g :for={tile <- @tiles} class={"ehv-a-rise ehv-i-#{tile.index + 4}"}>
<rect class="ehv-line-soft" x={tile.x + 0.5} y="122.5" width="108.3" height="47" rx="8" />
<rect
class={"ehv-fill-soft ehv-a-pulse ehv-i-#{tile.index + 3}"}
x={tile.x + 12}
y="134"
width="40"
height="5"
rx="2.5"
/>
<rect
class={"ehv-fill-strong ehv-a-pulse ehv-i-#{tile.index + 4}"}
x={tile.x + 12}
y="148"
width="64"
height="10"
rx="3"
/>
</g>
<g :for={row <- @items} class={"ehv-a-rise ehv-i-#{row.index + 7}"}>
<line class="ehv-line-faint" x1="24" x2="376" y1={row.y + 0.5} y2={row.y + 0.5} />
<g class={"ehv-a-pulse ehv-i-#{row.index + 7}"}>
<circle class="ehv-fill-soft" cx="31" cy={row.y + 12.5} r="6" />
<rect class="ehv-fill-soft" x="46" y={row.y + 9.5} width={row.w} height="6" rx="3" />
<rect class="ehv-fill-soft" x="328" y={row.y + 7.5} width="48" height="10" rx="5" />
</g>
</g>
<g class="ehv-a-fade ehv-i-10">
<line
class="ehv-line-faint"
x1="0.5"
x2="399.5"
y1={@status_y + 0.5}
y2={@status_y + 0.5}
/>
<text class="ehv-mono" x="24" y={@status_y + 17.5} font-size="8">
{String.upcase(@status)}
</text>
<text
class="ehv-mono ehv-t-lime"
x="376"
y={@status_y + 17.5}
font-size="8"
text-anchor="end"
>
{@percent}%
</text>
</g>
</g>
</svg>
</.frame>
"""
end
end
assets/css/browser/loading.css
/* browser/loading */
.ehv-browser-loading { --ehv-max-w: 440px; }
/* The spinner in the address bar turns around the centre of its own track. */
@keyframes ehv-browser-loading-spin { to { transform: rotate(360deg); } }
.ehv-browser-loading__spin { transform-box: fill-box; transform-origin: center; }
.ehv--animated .ehv-browser-loading__spin { animation: ehv-browser-loading-spin 1.1s linear infinite; }