empowered.house ↗

metrics/goal.ex

Goal

A company goal with its progress and the initiatives that move it.

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

<div class="h-96 rounded-xl border border-border">
  <.metrics_goal animated />
</div>
default
isometric
custom copy · all on track
hover · no glow
light surface
isometric · fade out · still

Props

Prop Type Default What it does
eyebrow string "Company goal · Q4"
title string "Ship every order in 48 hours"
progress integer 72 Percent of the way there, 0 to 100.
target integer 90 Where the target tick sits, 0 to 100.
caption string "Target 90% by December"
initiatives list [%{name: "Automate order intake", status: "on track"}, %{name: "Stock check in the ERP", status: "at risk"}, ...] Up to three `%{name, status}`. `on track` is lime, `at risk` is coral, anything else is neutral.
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/metrics/goal.ex
goal.ex
defmodule EHVisuals.Metrics.Goal do
  @moduledoc """
  A company goal with its progress and the initiatives that move it.
  """

  use EHVisuals.Composition,
    name: "metrics/goal",
    title: "Goal",
    description: "A company goal with its progress and the initiatives that move it."

  @initiatives [
    %{name: "Automate order intake", status: "on track"},
    %{name: "Stock check in the ERP", status: "at risk"},
    %{name: "Carrier booking agent", status: "planned"}
  ]

  attr(:eyebrow, :string, default: "Company goal · Q4")
  attr(:title, :string, default: "Ship every order in 48 hours")
  attr(:progress, :integer, default: 72, doc: "Percent of the way there, 0 to 100.")
  attr(:target, :integer, default: 90, doc: "Where the target tick sits, 0 to 100.")
  attr(:caption, :string, default: "Target 90% by December")

  attr(:initiatives, :list,
    default: @initiatives,
    doc:
      "Up to three `%{name, status}`. `on track` is lime, `at risk` is coral, anything else is neutral."
  )

  attr(:label, :string, default: "A company goal at 72 percent with three initiatives")
  frame_attrs()

  @spec metrics_goal(map()) :: Phoenix.LiveView.Rendered.t()
  def metrics_goal(assigns) do
    assigns =
      assigns
      |> assign(:fill_w, clamp(assigns.progress) * 3)
      |> assign(:target_x, 20 + clamp(assigns.target) * 3)
      |> assign(:rows, assigns.initiatives |> Enum.take(3) |> Enum.with_index())

    ~H"""
    <.frame name="metrics/goal" label={@label} {frame_opts(assigns)}>
      <svg viewBox="0 0 340 256">
        <g class="ehv-h-lift">
          <rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="339" height="255" rx="12" />
          <text class="ehv-mono ehv-a-fade ehv-i-1" x="20" y="31" font-size="9">
            {String.upcase(@eyebrow)}
          </text>
          <text class="ehv-t-strong ehv-a-fade ehv-i-2" x="20" y="56" font-size="15">{@title}</text>
          <text class="ehv-t-display ehv-a-fade ehv-i-3" x="20" y="101" font-size="30">
            {@progress}%
          </text>
          <text class="ehv-t-3 ehv-a-fade ehv-i-3" x="320" y="101" font-size="11.5" text-anchor="end">
            {@caption}
          </text>
          <rect class="ehv-fill-soft ehv-a-fade ehv-i-3" x="20" y="114" width="300" height="6" rx="3" />
          <rect
            class="ehv-fill-lime ehv-a-grow-x ehv-i-5"
            x="20"
            y="114"
            width={@fill_w}
            height="6"
            rx="3"
          />
          <line
            class="ehv-line-ink ehv-a-fade ehv-i-6"
            x1={@target_x}
            x2={@target_x}
            y1="108"
            y2="126"
          />
          <line class="ehv-line-faint" x1="20" x2="320" y1="144.5" y2="144.5" />
          <g :for={{row, index} <- @rows} class={"ehv-a-rise ehv-i-#{index + 6}"}>
            <circle class={dot(row.status)} cx="24" cy={168 + index * 32} r="3.5" />
            <text x="38" y={172 + index * 32} font-size="12.5">{row.name}</text>
            <text
              class={["ehv-mono", tone(row.status)]}
              x="320"
              y={172 + index * 32}
              font-size="8.5"
              text-anchor="end"
            >
              {String.upcase(row.status)}
            </text>
          </g>
        </g>
      </svg>
    </.frame>
    """
  end

  defp clamp(percent), do: percent |> max(0) |> min(100)

  defp dot("on track"), do: "ehv-fill-lime"
  defp dot("at risk"), do: "ehv-fill-coral"
  defp dot(_status), do: "ehv-fill-4"

  defp tone("on track"), do: "ehv-t-lime"
  defp tone("at risk"), do: "ehv-t-coral"
  defp tone(_status), do: nil
end
assets/css/metrics/goal.css
goal.css
/* metrics/goal */
.ehv-metrics-goal { --ehv-max-w: 360px; }