payments/checkout.ex
Checkout
A checkout summary with line items, a total and a pay button.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.payments_checkout animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
eyebrow |
string | "Order ORD-4471" |
|
items |
list | [%{name: "Label printer ZT-4", amount: "€1,180.00", detail: "2 pcs"}, %{name: "Thermal labels 100 x 150", ...}, ...] |
Up to five `%{name, detail, amount}`. Names up to 26 characters. |
extras |
list | [%{label: "Subtotal", amount: "€1,537.00"}, %{label: "VAT 23%", amount: "€353.51"}] |
Up to three `%{label, amount}` between the items and the total. May be empty. |
total_label |
string | "Total" |
|
total |
string | "€1,890.51" |
|
button |
string | "Pay €1,890.51" |
Up to 30 characters. |
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/payments/checkout.ex
defmodule EHVisuals.Payments.Checkout do
@moduledoc """
A checkout summary: the line items, what is added on top, the total and the
button that pays it.
"""
use EHVisuals.Composition,
name: "payments/checkout",
title: "Checkout",
description: "A checkout summary with line items, a total and a pay button."
@items [
%{name: "Label printer ZT-4", detail: "2 pcs", amount: "€1,180.00"},
%{name: "Thermal labels 100 x 150", detail: "40 rolls", amount: "€312.00"},
%{name: "Delivery to dock 4", detail: "Northwind, 2 days", amount: "€45.00"}
]
attr(:eyebrow, :string, default: "Order ORD-4471")
attr(:items, :list,
default: @items,
doc: "Up to five `%{name, detail, amount}`. Names up to 26 characters."
)
attr(:extras, :list,
default: [%{label: "Subtotal", amount: "€1,537.00"}, %{label: "VAT 23%", amount: "€353.51"}],
doc: "Up to three `%{label, amount}` between the items and the total. May be empty."
)
attr(:total_label, :string, default: "Total")
attr(:total, :string, default: "€1,890.51")
attr(:button, :string, default: "Pay €1,890.51", doc: "Up to 30 characters.")
attr(:label, :string, default: "A checkout summary with three line items, VAT and a pay button")
frame_attrs()
@spec payments_checkout(map()) :: Phoenix.LiveView.Rendered.t()
def payments_checkout(assigns) do
items = assigns.items |> Enum.take(5) |> Enum.with_index()
extras = assigns.extras |> Enum.take(3) |> Enum.with_index()
extras_y = 48 + length(items) * 38 + 4
total_y = if extras == [], do: extras_y, else: extras_y + 8 + length(extras) * 22 + 4
button_y = total_y + 44
assigns =
assigns
|> assign(:rows, items)
|> assign(:extra_rows, extras)
|> assign(:count, if(length(items) == 1, do: "1 item", else: "#{length(items)} items"))
|> assign(:extras_y, extras_y)
|> assign(:total_y, total_y)
|> assign(:button_y, button_y)
|> assign(:height, button_y + 36 + 20)
~H"""
<.frame name="payments/checkout" label={@label} {frame_opts(assigns)}>
<svg viewBox={"0 0 340 #{@height}"}>
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="339" height={@height - 1} rx="12" />
<g class="ehv-a-fade ehv-i-1">
<text class="ehv-mono" x="20" y="31" font-size="9">{String.upcase(@eyebrow)}</text>
<text class="ehv-mono ehv-t-4" x="320" y="31" font-size="9" text-anchor="end">
{String.upcase(@count)}
</text>
</g>
<g :for={{item, index} <- @rows} class={"ehv-a-rise ehv-i-#{index + 2}"}>
<rect class="ehv-card-3" x="20.5" y={53.5 + index * 38} width="27" height="27" rx="7" />
<rect class="ehv-node-soft" x="29.5" y={62.5 + index * 38} width="9" height="9" rx="2.5" />
<text x="58" y={65 + index * 38} font-size="12.5">{item.name}</text>
<text class="ehv-mono" x="58" y={78 + index * 38} font-size="8">
{String.upcase(item.detail)}
</text>
<text x="320" y={71 + index * 38} font-size="12.5" text-anchor="end">{item.amount}</text>
</g>
<g class="ehv-a-fade ehv-i-7">
<line class="ehv-line-faint" x1="20" x2="320" y1={@extras_y + 0.5} y2={@extras_y + 0.5} />
<g :for={{extra, index} <- @extra_rows}>
<text class="ehv-t-3" x="20" y={@extras_y + 23 + index * 22} font-size="11.5">
{extra.label}
</text>
<text
class="ehv-t-2"
x="320"
y={@extras_y + 23 + index * 22}
font-size="11.5"
text-anchor="end"
>
{extra.amount}
</text>
</g>
<line
:if={@extra_rows != []}
class="ehv-line-faint"
x1="20"
x2="320"
y1={@total_y + 0.5}
y2={@total_y + 0.5}
/>
</g>
<g class="ehv-a-fade ehv-i-8">
<text class="ehv-t-strong" x="20" y={@total_y + 28} font-size="13">{@total_label}</text>
<text
class="ehv-t-display"
x="320"
y={@total_y + 30}
font-size="20"
text-anchor="end"
>
{@total}
</text>
</g>
<g class="ehv-a-rise ehv-i-9">
<rect
class="ehv-fill-lime ehv-payments-checkout__pay"
x="20"
y={@button_y}
width="300"
height="36"
rx="8"
/>
<text
class="ehv-t-strong ehv-t-on-lime ehv-payments-checkout__pay-text"
x="170"
y={@button_y + 22.5}
font-size="12.5"
text-anchor="middle"
>
{@button}
</text>
</g>
</g>
</svg>
</.frame>
"""
end
end
assets/css/payments/checkout.css
/* payments/checkout */
.ehv-payments-checkout { --ehv-max-w: 370px; }
.ehv-payments-checkout__pay-text { pointer-events: none; }
.ehv--hover .ehv-payments-checkout__pay { transition: fill 200ms var(--ehv-ease); }
.ehv--hover .ehv-payments-checkout__pay:hover { fill: var(--ehv-ink); }