sections/newsletter.ex
Newsletter signup
A newsletter signup with a heading, a line of copy, an email input with a lime button and a small note.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.sections_newsletter animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
eyebrow |
string | "Newsletter" |
|
title |
string | "Operations notes, once a month" |
Up to 44 characters. |
email |
string | "marta@northwind.com" |
What is typed into the input, up to 30 characters. Empty shows the placeholder. |
placeholder |
string | "you@company.com" |
|
action |
string | "Subscribe" |
The button. Up to 12 characters. |
note |
any | "One email a month. Unsubscribe any time." |
The line under the input, up to 60 characters. `nil` hides it. |
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/newsletter.ex
defmodule EHVisuals.Sections.Newsletter do
@moduledoc """
A newsletter signup section: a heading, a line of copy, an email input with
a lime button inside it, and a small note underneath.
"""
use EHVisuals.Composition,
name: "sections/newsletter",
title: "Newsletter signup",
description:
"A newsletter signup with a heading, a line of copy, an email input with a lime button and a small note."
attr(:eyebrow, :string, default: "Newsletter")
attr(:title, :string, default: "Operations notes, once a month", doc: "Up to 44 characters.")
attr(:email, :string,
default: "marta@northwind.com",
doc: "What is typed into the input, up to 30 characters. Empty shows the placeholder."
)
attr(:placeholder, :string, default: "you@company.com")
attr(:action, :string, default: "Subscribe", doc: "The button. Up to 12 characters.")
attr(:note, :any,
default: "One email a month. Unsubscribe any time.",
doc: "The line under the input, up to 60 characters. `nil` hides it."
)
attr(:label, :string, default: "A newsletter signup with an email input and a subscribe button")
frame_attrs()
@spec sections_newsletter(map()) :: Phoenix.LiveView.Rendered.t()
def sections_newsletter(assigns) do
typed = assigns.email != ""
action_w = Float.round(24 + String.length(assigns.action) * 6.0, 1)
assigns =
assigns
|> assign(:typed, typed)
|> assign(:action_w, action_w)
|> assign(:action_x, 406 - action_w)
|> assign(:caret_x, Float.round(108 + String.length(assigns.email) * 6.55, 1))
|> assign(:height, if(assigns.note, do: 204, else: 184))
~H"""
<.frame name="sections/newsletter" label={@label} {frame_opts(assigns)}>
<svg viewBox={"0 0 480 #{@height}"}>
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="479" height={@height - 1} rx="12" />
<text
class="ehv-mono ehv-a-fade ehv-i-1"
x="240"
y="38"
font-size="8.5"
text-anchor="middle"
>
{String.upcase(@eyebrow)}
</text>
<text
class="ehv-t-strong ehv-a-fade ehv-i-2"
x="240"
y="62"
font-size="16"
text-anchor="middle"
>
{@title}
</text>
<g class="ehv-a-fade ehv-i-3">
<rect class="ehv-fill-soft" x="112" y="78" width="256" height="5" rx="2.5" />
<rect class="ehv-fill-soft" x="144" y="90" width="192" height="5" rx="2.5" />
</g>
<g class="ehv-a-rise ehv-i-4">
<rect
class={[
"ehv-well ehv-sections-newsletter__input",
@typed && "ehv-sections-newsletter__input--focus"
]}
x="70.5"
y="116.5"
width="339"
height="33"
rx="9"
/>
<path
class="ehv-line-ink ehv-round"
d="M83.5 127.5 h13 v11 h-13 z M83.5 128.5 l6.5 5 l6.5 -5"
/>
<text :if={@typed} x="105" y="137.5" font-size="12">{@email}</text>
<text :if={!@typed} class="ehv-t-3" x="105" y="137.5" font-size="12">{@placeholder}</text>
<line
:if={@typed}
class="ehv-line-lime ehv-sections-newsletter__caret"
x1={@caret_x}
x2={@caret_x}
y1="126"
y2="140"
/>
</g>
<g class="ehv-a-pop ehv-i-6">
<rect class="ehv-fill-lime" x={@action_x} y="120" width={@action_w} height="26" rx="7" />
<text
class="ehv-t-strong ehv-t-on-lime"
x={@action_x + @action_w / 2}
y="137"
font-size="11"
text-anchor="middle"
>
{@action}
</text>
</g>
<text
:if={@note}
class="ehv-t-3 ehv-a-fade ehv-i-7"
x="240"
y="174"
font-size="10.5"
text-anchor="middle"
>
{@note}
</text>
</g>
</svg>
</.frame>
"""
end
end
assets/css/sections/newsletter.css
/* sections/newsletter */
.ehv-sections-newsletter { --ehv-max-w: 520px; --ehv-iso-scale: 0.82; }
.ehv .ehv-sections-newsletter__input--focus { stroke: var(--ehv-lime); }
@keyframes ehv-sections-newsletter-blink { 50% { opacity: 0; } }
.ehv--animated .ehv-sections-newsletter__caret {
animation: ehv-sections-newsletter-blink 1.1s steps(1) infinite;
}
.ehv--hover .ehv-sections-newsletter__input { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover:hover .ehv-sections-newsletter__input { stroke: var(--ehv-lime); }