code/snippet.ex
Snippet
A copyable code snippet card with a language label and a copy button.
# in the module that renders the template
use EHVisuals
<div class="h-96 rounded-xl border border-border">
<.code_snippet animated />
</div>
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
title |
string | "Create an order" |
Up to 26 characters. |
language |
string | "Elixir" |
Up to 10 characters. |
lines |
list | ["{:ok, order} =", " EHOS.Orders.create(%{", " customer: \"Northwind\",", " total: 18_400", " })"] |
Four to six lines of code as strings (up to eight), up to 50 characters each. |
copied |
boolean | false |
Shows the button right after a click. |
copy |
string | "Copy" |
|
copied_text |
string | "Copied" |
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/code/snippet.ex
defmodule EHVisuals.Code.Snippet do
@moduledoc """
A code snippet to copy: a title, the language, a copy button and a few
lines of code.
"""
use EHVisuals.Composition,
name: "code/snippet",
title: "Snippet",
description: "A copyable code snippet card with a language label and a copy button."
@lines [
"{:ok, order} =",
" EHOS.Orders.create(%{",
" customer: \"Northwind\",",
" total: 18_400",
" })"
]
# Tone of each token kind, in the order of the groups in token_pattern/0.
@tones ["ehv-t-3", "ehv-t-lime", nil, "ehv-t-strong", nil, nil, "ehv-t-2", "ehv-t-3"]
# Width of one character at font size 11.
@char 6.6
attr(:title, :string, default: "Create an order", doc: "Up to 26 characters.")
attr(:language, :string, default: "Elixir", doc: "Up to 10 characters.")
attr(:lines, :list,
default: @lines,
doc: "Four to six lines of code as strings (up to eight), up to 50 characters each."
)
attr(:copied, :boolean, default: false, doc: "Shows the button right after a click.")
attr(:copy, :string, default: "Copy")
attr(:copied_text, :string, default: "Copied")
attr(:label, :string, default: "A code snippet in Elixir with a copy button")
frame_attrs()
@spec code_snippet(map()) :: Phoenix.LiveView.Rendered.t()
def code_snippet(assigns) do
rows =
for {line, index} <- assigns.lines |> Enum.take(8) |> Enum.with_index() do
%{y: 70 + index * 22, tokens: tokens(line), step: min(index + 3, 24)}
end
button = if assigns.copied, do: assigns.copied_text, else: assigns.copy
button_w = Float.round(34 + String.length(button) * 5.8, 1)
language_w = Float.round(16 + String.length(assigns.language) * 5.5, 1)
assigns =
assigns
|> assign(:rows, rows)
|> assign(:button, button)
|> assign(:button_w, button_w)
|> assign(:button_x, Float.round(360 - button_w, 1))
|> assign(:language_w, language_w)
|> assign(:height, 44 + 12 + length(rows) * 22 + 12)
~H"""
<.frame name="code/snippet" label={@label} {frame_opts(assigns)}>
<svg viewBox={"0 0 380 #{@height}"}>
<g class="ehv-h-lift">
<rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="379" height={@height - 1} rx="12" />
<g class="ehv-a-fade ehv-i-1">
<rect class="ehv-card-3" x="20.5" y="12.5" width={@language_w} height="19" rx="5" />
<text
class="ehv-mono ehv-t-2"
x={20.5 + @language_w / 2}
y="25"
font-size="7.5"
text-anchor="middle"
>
{String.upcase(@language)}
</text>
<text class="ehv-t-2" x={@language_w + 31} y="26" font-size="12">{@title}</text>
<line class="ehv-line-soft" x1="0.5" x2="379.5" y1="44.5" y2="44.5" />
</g>
<g class="ehv-a-fade ehv-i-2">
<rect
class={[
"ehv-code-snippet__button",
@copied && "ehv-code-snippet__button--copied"
]}
x={@button_x}
y="11.5"
width={@button_w}
height="21"
rx="6"
/>
<path
:if={@copied}
class="ehv-line-lime ehv-round"
d={"M#{@button_x + 10} 22 l2.8 2.8 l5 -5.6"}
/>
<g :if={not @copied}>
<rect class="ehv-line-ink" x={@button_x + 9.5} y="16.5" width="7" height="8" rx="2" />
<path
class="ehv-line-ink ehv-round"
d={"M#{@button_x + 12.5} 27.5 h5 a2 2 0 0 0 2 -2 v-5.5"}
/>
</g>
<text
class={["ehv-mono", if(@copied, do: "ehv-t-lime", else: "ehv-t-2")]}
x={@button_x + 25}
y="25"
font-size="8"
>
{String.upcase(@button)}
</text>
</g>
<g :for={row <- @rows} class={"ehv-a-fade ehv-i-#{row.step}"}>
<text
:for={token <- row.tokens}
class={["ehv-code", token.tone]}
x={token.x}
y={row.y}
font-size="11"
>
{token.text}
</text>
</g>
</g>
</svg>
</.frame>
"""
end
# Splits a line into tokens, each with its column. SVG text drops runs of
# spaces, so every token is placed by its own x.
defp tokens(line) do
token_pattern()
|> Regex.scan(line, return: :index)
|> Enum.flat_map(fn [{start, length} | groups] ->
case Enum.find_index(groups, fn {position, _length} -> position >= 0 end) do
2 ->
[]
kind ->
[
%{
text: binary_part(line, start, length),
x: Float.round(20 + start * @char, 1),
tone: Enum.at(@tones, kind || 7)
}
]
end
end)
end
# Groups: comment, string, space, keyword, atom or key, module or number,
# name, any other character.
defp token_pattern do
~r/(#.*)|("(?:[^"\\]|\\.)*")|(\s+)|(\b(?:defmodule|defp|def|do|end|if|else|fn|case|cond|when|with|alias|import|use|nil|true|false)\b)|(:[a-z_]\w*[?!]?|[a-z_]\w*[?!]?:(?!:))|([A-Z]\w*|\d[\d_.]*)|(@?[a-z_]\w*[?!]?)|(.)/
end
end
assets/css/code/snippet.css
/* code/snippet */
.ehv-code-snippet { --ehv-max-w: 410px; --ehv-step: 80ms; }
.ehv-code-snippet__button { fill: var(--ehv-canvas); stroke: var(--ehv-border-strong); stroke-width: 1; }
.ehv-code-snippet__button--copied { fill: var(--ehv-lime-soft); stroke: var(--ehv-lime); }
.ehv--hover .ehv-code-snippet__button { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover:hover .ehv-code-snippet__button { stroke: var(--ehv-lime); }