empowered.house ↗

connections/hub.ex

Hub

The company's systems arranged around one hub, each sending its records in.

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

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

Props

Prop Type Default What it does
systems list ["ERP", "CRM", "Mail", "Sheets", "Bank", "WMS"] Three to eight short system names.
center_label string "EHOS"
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/connections/hub.ex
hub.ex
defmodule EHVisuals.Connections.Hub do
  @moduledoc """
  The company's systems around one hub, each sending its records in.
  """

  use EHVisuals.Composition,
    name: "connections/hub",
    title: "Hub",
    description: "The company's systems arranged around one hub, each sending its records in."

  attr(:systems, :list,
    default: ~w(ERP CRM Mail Sheets Bank WMS),
    doc: "Three to eight short system names."
  )

  attr(:center_label, :string, default: "EHOS")
  attr(:label, :string, default: "Six company systems connected to EHOS in the middle")
  frame_attrs()

  @spec connections_hub(map()) :: Phoenix.LiveView.Rendered.t()
  def connections_hub(assigns) do
    count = max(length(assigns.systems), 1)

    items =
      for {name, index} <- Enum.with_index(assigns.systems) do
        angle = (-90 + index * 360 / count) * :math.pi() / 180

        %{
          name: name,
          index: index,
          x: Float.round(200 + 150 * :math.cos(angle), 1),
          y: Float.round(160 + 114 * :math.sin(angle), 1)
        }
      end

    assigns =
      assigns
      |> assign(:items, items)
      |> assign(:label_w, 14 + String.length(assigns.center_label) * 6.2)

    ~H"""
    <.frame name="connections/hub" label={@label} {frame_opts(assigns)}>
      <svg viewBox="0 0 400 320">
        <g class="ehv-h-lift">
          <ellipse class="ehv-line-faint ehv-a-fade ehv-i-1" cx="200" cy="160" rx="112" ry="84" />
          <ellipse class="ehv-line-faint ehv-a-fade ehv-i-2" cx="200" cy="160" rx="66" ry="50" />
          <line
            :for={item <- @items}
            class={"ehv-line ehv-a-draw ehv-i-#{item.index + 2}"}
            x1={item.x}
            y1={item.y}
            x2="200"
            y2="160"
            pathLength="1"
          />
          <g :if={@animated} class="ehv-smil ehv-a-fade ehv-i-12">
            <circle :for={item <- @items} class="ehv-fill-lime" r="2.5">
              <animate
                attributeName="cx"
                from={item.x}
                to="200"
                dur="2.8s"
                begin={"#{item.index * 0.45}s"}
                repeatCount="indefinite"
              />
              <animate
                attributeName="cy"
                from={item.y}
                to="160"
                dur="2.8s"
                begin={"#{item.index * 0.45}s"}
                repeatCount="indefinite"
              />
              <animate
                attributeName="opacity"
                values="0;1;1;0"
                keyTimes="0;0.25;0.7;1"
                dur="2.8s"
                begin={"#{item.index * 0.45}s"}
                repeatCount="indefinite"
              />
            </circle>
          </g>
          <g :for={item <- @items} class={"ehv-a-pop ehv-i-#{item.index + 3}"}>
            <rect
              class="ehv-card ehv-connections-hub__chip"
              x={item.x - 42}
              y={item.y - 15}
              width="84"
              height="30"
              rx="8"
            />
            <text
              class="ehv-mono ehv-t-2"
              x={item.x}
              y={item.y + 3.5}
              font-size="9"
              text-anchor="middle"
            >
              {String.upcase(item.name)}
            </text>
          </g>
          <g class="ehv-a-pop">
            <rect class="ehv-card-3" x="164" y="124" width="72" height="72" rx="18" />
            <.mark x="172" y="132" size="56" />
          </g>
          <g class="ehv-a-fade ehv-i-4">
            <rect
              class="ehv-fill-canvas"
              x={200 - @label_w / 2}
              y="203"
              width={@label_w}
              height="16"
              rx="5"
            />
            <text class="ehv-mono ehv-t-lime" x="200" y="214" font-size="8.5" text-anchor="middle">
              {String.upcase(@center_label)}
            </text>
          </g>
        </g>
      </svg>
    </.frame>
    """
  end
end
assets/css/connections/hub.css
hub.css
/* connections/hub */
.ehv-connections-hub { --ehv-max-w: 440px; }

.ehv--hover .ehv-connections-hub__chip { transition: stroke 200ms var(--ehv-ease); }
.ehv--hover .ehv-connections-hub__chip:hover { stroke: var(--ehv-lime); }