sections/auth.ex
Auth section
A login section in miniature: a single sign-on button, an email field, a password field and a sign-in button.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.sections_auth animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
brand |
string | "EHOS" |
The name next to the mark. |
heading |
string | "Welcome back" |
Breaks into at most two lines of 17 characters. |
text |
string | "Sign in to see today's orders, approvals and inv" <> ... |
Breaks into at most three lines of 30 characters. |
sso |
string | "Continue with SSO" |
22 characters at most. |
email |
string | "marta.nowak@northwind.com" |
27 characters at most. |
password |
integer | 9 |
How many characters are typed in, 0 to 16. |
action |
string | "Sign 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/sections/auth.ex
defmodule EHVisuals.Sections.Auth do
@moduledoc """
A login section in miniature: who you are signing in to on the left, and a
form with single sign-on, an email, a password and the sign-in button.
"""
use EHVisuals.Composition,
name: "sections/auth",
title: "Auth section",
description:
"A login section in miniature: a single sign-on button, an email field, a password field and a sign-in button."
attr(:brand, :string, default: "EHOS", doc: "The name next to the mark.")
attr(:heading, :string,
default: "Welcome back",
doc: "Breaks into at most two lines of 17 characters."
)
attr(:text, :string,
default: "Sign in to see today's orders, approvals and invoices.",
doc: "Breaks into at most three lines of 30 characters."
)
attr(:sso, :string, default: "Continue with SSO", doc: "22 characters at most.")
attr(:email, :string, default: "marta.nowak@northwind.com", doc: "27 characters at most.")
attr(:password, :integer, default: 9, doc: "How many characters are typed in, 0 to 16.")
attr(:action, :string, default: "Sign in")
attr(:label, :string,
default:
"A login section with a single sign-on button, an email, a password and a sign-in button"
)
frame_attrs()
@spec sections_auth(map()) :: Phoenix.LiveView.Rendered.t()
def sections_auth(assigns) do
heading = wrap(assigns.heading, 17, 2)
text = wrap(assigns.text, 30, 3)
text_y = 96 + (length(heading) - 1) * 25 + 28
dots = assigns.password |> max(0) |> min(16)
sso_w = 20 + String.length(assigns.sso) * 6.0
assigns =
assigns
|> assign(:heading_lines, Enum.with_index(heading))
|> assign(:text_lines, Enum.with_index(text))
|> assign(:text_y, text_y)
|> assign(:dots, if(dots > 0, do: Enum.map(0..(dots - 1), &(266 + &1 * 9)), else: []))
|> assign(:caret_x, 264 + dots * 9)
|> assign(:sso_x, Float.round(346 - sso_w / 2, 1))
~H"""
<.frame name="sections/auth" label={@label} {frame_opts(assigns)}>
<svg viewBox="0 0 480 300">
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="479" height="299" rx="12" />
<g class="ehv-a-fade ehv-i-1">
<.mark x="21" y="21" size="32" />
<text class="ehv-mono ehv-t-ink" x="54" y="40.5" font-size="9">
{String.upcase(@brand)}
</text>
</g>
<text
:for={{line, index} <- @heading_lines}
class="ehv-t-display ehv-a-fade ehv-i-2"
x="28"
y={96 + index * 25}
font-size="21"
>
{line}
</text>
<text
:for={{line, index} <- @text_lines}
class="ehv-t-2 ehv-a-fade ehv-i-3"
x="28"
y={@text_y + index * 16}
font-size="11.5"
>
{line}
</text>
<g class="ehv-a-fade ehv-i-4">
<line class="ehv-line-faint" x1="28" x2="208" y1="252.5" y2="252.5" />
<text class="ehv-mono" x="28" y="272" font-size="7.5">
NO ACCOUNT? ASK YOUR ADMIN
</text>
</g>
<g class="ehv-a-rise ehv-i-2">
<rect class="ehv-card-2" x="236.5" y="24.5" width="219" height="251" rx="10" />
</g>
<g class="ehv-a-rise ehv-i-3">
<rect
class="ehv-card-3 ehv-sections-auth__sso"
x="252.5"
y="40.5"
width="187"
height="31"
rx="8"
/>
<path
class="ehv-line-ink ehv-round"
transform={"translate(#{@sso_x} 50)"}
d="M4 3.4a2.6 2.6 0 1 0 0 5.2a2.6 2.6 0 0 0 0-5.2z M6.6 6h4.9 M9.3 6v2 M11.5 6v1.6"
/>
<text class="ehv-t-strong" x={@sso_x + 19} y="60" font-size="11.5">{@sso}</text>
</g>
<g class="ehv-a-fade ehv-i-4">
<line class="ehv-line-soft" x1="252" x2="330" y1="88.5" y2="88.5" />
<text class="ehv-mono" x="346" y="91" font-size="7.5" text-anchor="middle">OR</text>
<line class="ehv-line-soft" x1="362" x2="440" y1="88.5" y2="88.5" />
</g>
<g class="ehv-a-rise ehv-i-5">
<text class="ehv-mono" x="252" y="110" font-size="7.5">WORK EMAIL</text>
<rect class="ehv-well" x="252.5" y="116.5" width="187" height="29" rx="7" />
<text class="ehv-t-2" x="262" y="135" font-size="11.5">{@email}</text>
</g>
<g class="ehv-a-rise ehv-i-6">
<text class="ehv-mono" x="252" y="164" font-size="7.5">PASSWORD</text>
<rect
class="ehv-well ehv-sections-auth__field--focus"
x="252.5"
y="170.5"
width="187"
height="29"
rx="7"
/>
<g
:for={{x, index} <- Enum.with_index(@dots)}
class={"ehv-a-pop ehv-i-#{min(index + 8, 24)}"}
>
<circle class="ehv-fill-ink" cx={x} cy="185" r="2.4" />
</g>
<line class="ehv-line-lime ehv-a-pulse" x1={@caret_x} x2={@caret_x} y1="178" y2="192" />
</g>
<g class="ehv-a-rise ehv-i-7">
<rect
class="ehv-fill-lime ehv-sections-auth__submit"
x="252"
y="214"
width="188"
height="32"
rx="8"
/>
<text
class="ehv-t-strong ehv-t-on-lime"
x="346"
y="234"
font-size="12"
text-anchor="middle"
>
{@action}
</text>
</g>
<text
class="ehv-mono ehv-a-fade ehv-i-8"
x="346"
y="264"
font-size="7.5"
text-anchor="middle"
>
FORGOT PASSWORD?
</text>
</g>
</svg>
</.frame>
"""
end
# Breaks text into lines of at most `width` characters, at most `limit` lines.
defp wrap(text, width, limit) do
text
|> String.split(" ", trim: true)
|> Enum.reduce([], fn
word, [line | rest] ->
if String.length(line) + 1 + String.length(word) <= width,
do: [line <> " " <> word | rest],
else: [word, line | rest]
word, [] ->
[word]
end)
|> Enum.reverse()
|> Enum.take(limit)
end
end
assets/css/sections/auth.css
/* sections/auth */
.ehv-sections-auth { --ehv-max-w: 540px; --ehv-iso-scale: 0.82; }
/* The field being typed in. */
.ehv .ehv-sections-auth__field--focus { stroke: var(--ehv-ink-3); }
.ehv--hover .ehv-sections-auth__sso { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover .ehv-sections-auth__sso:hover { stroke: var(--ehv-ink-2); }
.ehv--hover .ehv-sections-auth__submit { transition: opacity 200ms var(--ehv-ease); }
.ehv--hover .ehv-sections-auth__submit:hover { opacity: 0.86; }