empowered.house ↗

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.

Use it
# in the module that renders the template
use EHVisuals

<div class="h-96 rounded-xl border border-border">
  <.sections_auth animated />
</div>
default
isometric
custom copy
empty password · hover
light surface
isometric · fade out · still

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
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
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; }