empowered.house ↗

code/snippet.ex

Snippet

A copyable code snippet card with a language label and a copy button.

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

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

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