empowered.house ↗

files/simple.ex

File card

A single file card with a page icon, the file name, its type and size, and a line about where it came from.

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

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

Props

Prop Type Default What it does
name string "Northwind-4471.pdf" Up to 28 characters.
type string "PDF" The badge on the page. Up to 5 characters.
size string "248 KB"
note string "Added by the intake agent · 09:12" The line under the type and size. Up to 44 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/files/simple.ex
simple.ex
defmodule EHVisuals.Files.Simple do
  @moduledoc """
  One file as a card: a page icon with its type, the file name, the type and
  size, and a line about where it came from.
  """

  use EHVisuals.Composition,
    name: "files/simple",
    title: "File card",
    description:
      "A single file card with a page icon, the file name, its type and size, and a line about where it came from."

  @bone_widths [26, 42, 42, 34, 42, 30]

  attr(:name, :string, default: "Northwind-4471.pdf", doc: "Up to 28 characters.")
  attr(:type, :string, default: "PDF", doc: "The badge on the page. Up to 5 characters.")
  attr(:size, :string, default: "248 KB")

  attr(:note, :string,
    default: "Added by the intake agent · 09:12",
    doc: "The line under the type and size. Up to 44 characters."
  )

  attr(:label, :string, default: "A file card for a PDF invoice with its name, type and size")

  frame_attrs()

  @spec files_simple(map()) :: Phoenix.LiveView.Rendered.t()
  def files_simple(assigns) do
    badge_w = Float.round(14 + String.length(assigns.type) * 5.6, 1)

    assigns =
      assigns
      |> assign(:badge_w, badge_w)
      |> assign(:badge_x, Float.round(188 - badge_w, 1))
      |> assign(:bones, Enum.with_index(@bone_widths))
      |> assign(:meta, "#{assigns.type} · #{assigns.size}")

    ~H"""
    <.frame name="files/simple" label={@label} {frame_opts(assigns)}>
      <svg viewBox="0 0 300 206">
        <g class="ehv-h-lift">
          <rect class="ehv-card ehv-a-rise" x="0.5" y="0.5" width="299" height="205" rx="12" />
          <rect class="ehv-well ehv-a-fade ehv-i-1" x="12.5" y="12.5" width="275" height="110" rx="8" />
          <g class="ehv-a-rise ehv-i-2">
            <g class="ehv-files-simple__page">
              <path class="ehv-card-2 ehv-round" d="M120.5 24.5 h44 l15 15 v70 h-59 z" />
              <path class="ehv-line ehv-round" d="M164.5 24.5 v15 h15" />
              <g :for={{width, index} <- @bones} class={"ehv-a-fade ehv-i-#{index + 3}"}>
                <rect
                  class={[
                    if(index == 0, do: "ehv-fill-strong", else: "ehv-fill-soft"),
                    "ehv-a-grow-x",
                    "ehv-i-#{index + 3}"
                  ]}
                  x="129"
                  y={46 + index * 9}
                  width={width}
                  height="4"
                  rx="2"
                />
              </g>
              <g class="ehv-a-pop ehv-i-8">
                <rect class="ehv-fill-lime" x={@badge_x} y="88" width={@badge_w} height="17" rx="5" />
                <text
                  class="ehv-mono ehv-t-on-lime"
                  x={@badge_x + @badge_w / 2}
                  y="99.5"
                  font-size="7.5"
                  text-anchor="middle"
                >
                  {String.upcase(@type)}
                </text>
              </g>
            </g>
          </g>
          <g class="ehv-a-fade ehv-i-6">
            <text class="ehv-t-strong" x="20" y="150" font-size="14">{@name}</text>
            <text class="ehv-mono ehv-t-2" x="20" y="167" font-size="8.5">
              {String.upcase(@meta)}
            </text>
            <text class="ehv-t-3" x="20" y="188" font-size="11">{@note}</text>
          </g>
          <g class="ehv-a-pop ehv-i-9">
            <circle class="ehv-line-soft" cx="266" cy="154" r="13" />
            <path
              class="ehv-line-ink ehv-round ehv-files-simple__arrow"
              d="M266 147.5 v9.5 m-4 -4 l4 4 l4 -4"
            />
            <path class="ehv-line-ink ehv-round" d="M260.5 160.5 h11" />
          </g>
        </g>
      </svg>
    </.frame>
    """
  end
end
assets/css/files/simple.css
simple.css
/* files/simple */
.ehv-files-simple { --ehv-max-w: 340px; }

/* On hover the page lifts off its well and the download arrow dips. */
.ehv-files-simple__page,
.ehv-files-simple__arrow { transform-box: fill-box; }
.ehv--hover :is(.ehv-files-simple__page, .ehv-files-simple__arrow) { transition: transform 200ms var(--ehv-ease); }
.ehv--hover:hover .ehv-files-simple__page { transform: translateY(-3px); }
.ehv--hover:hover .ehv-files-simple__arrow { transform: translateY(1.5px); }