Class: Plutonium::UI::Avatar

Inherits:
Component::Base show all
Defined in:
lib/plutonium/ui/avatar.rb

Overview

Renders a profile/avatar image for a subject.

Avatar(user)                     # Navii fallback seeded from the record
Avatar(user, src: :photo)        # user.photo if present, else Navii fallback
Avatar(user, src: user.photo)    # pass the attachment/uploader/URL directly
Avatar("acme-team")              # a String subject is a deterministic seed
Avatar("https://.../p.png")      # a URL-shaped subject is shown as the image
Avatar(src: "https://.../p.png") # a bare image, no subject/fallback

The positional subject is the identity the fallback is derived from: a record or a String, hashed to an opaque, PII-free seed. As a convenience, a URL-shaped String subject is treated as src (the image) instead. src is the image to show and may be:

  • a Symbol naming a method on the subject (e.g. :avatar -> subject.avatar). This is a contract: the subject must respond to it (raises NoMethodError otherwise), so only use a Symbol src with a record subject.

  • an ActiveStorage attachment, active_shrine/Shrine uploader, or URL String

Resolution order: the resolved src, then a Navii avatar seeded from the subject, then a generic user icon when there is nothing to show.

Constant Summary collapse

SIZES =

Pixel dimensions per semantic size, plus the matching Tailwind width/height utilities (needed because the preflight resets ‘img { height: auto }`, so width/height attributes alone don’t pin the rendered size).

{xs: 24, sm: 32, md: 40, lg: 48, xl: 64}.freeze
SIZE_CLASSES =
{xs: "w-6 h-6", sm: "w-8 h-8", md: "w-10 h-10", lg: "w-12 h-12", xl: "w-16 h-16"}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Component::Behaviour

#around_template

Methods included from Component::Tokens

#classes, #tokens

Methods included from Component::Kit

#BuildActionButton, #BuildActionsDropdown, #BuildAvatar, #BuildBlock, #BuildBreadcrumbs, #BuildBulkActionsToolbar, #BuildColorModeSelector, #BuildDynaFrameContent, #BuildDynaFrameHost, #BuildEmptyCard, #BuildFrameNavigatorPanel, #BuildModalCentered, #BuildModalSlideover, #BuildPageHeader, #BuildPanel, #BuildRowActionsDropdown, #BuildSkeletonTable, #BuildTabList, #BuildTableFilterPills, #BuildTableInfo, #BuildTablePagination, #BuildTableScopesBar, #BuildTableScopesPills, #BuildTableSearchBar, #BuildTableToolbar, #BuildTableViewSwitcher, #method_missing, #respond_to_missing?

Constructor Details

#initialize(subject = nil, src: nil, size: :md, alt: nil, **attributes) ⇒ Avatar

Returns a new instance of Avatar.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/plutonium/ui/avatar.rb', line 71

def initialize(subject = nil, src: nil, size: :md, alt: nil, **attributes)
  # A URL-shaped positional subject is really an image, not an identity:
  # route it to src so Avatar("https://…/p.png") shows the image rather
  # than hashing the URL into a seed.
  if src.nil? && subject.is_a?(String) && subject.start_with?("http", "/")
    src = subject
    subject = nil
  end

  @subject = subject
  @src = src
  @size = size
  @alt = alt
  @attributes = attributes
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Plutonium::UI::Component::Kit

Class Method Details

.resolve_image_src(value, helpers = nil) ⇒ Object

Resolve an image value to a URL string. Supports:

  • ActiveStorage attachments -> helpers.url_for (they aren’t routable via #url)

  • active_shrine / other ActiveStorage-style wrappers -> value.url

  • Bare Shrine::UploadedFile, CarrierWave, etc. (respond to :url) -> value.url

  • Plain URL strings (“https://…” or “/uploads/…”)

Exposed as a module method so collaborators (e.g. Grid::Card) can reuse the resolution without instantiating the component.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/plutonium/ui/avatar.rb', line 43

def self.resolve_image_src(value, helpers = nil)
  return nil if value.nil?

  # ActiveStorage is the only supported source that must go through Rails
  # routing rather than its own #url. It has to be matched *before* the
  # generic attached?/url checks, because ActiveStorage-compatible wrappers
  # (e.g. active_shrine) respond to BOTH attached? and url, and those should
  # resolve via their own #url instead.
  if active_storage_attachment?(value)
    return value.attached? ? helpers&.url_for(value) : nil
  end

  if value.respond_to?(:attached?)  # active_shrine & other AS-style wrappers
    value.attached? ? value.url : nil
  elsif value.respond_to?(:url)     # bare Shrine::UploadedFile, CarrierWave, ...
    value.url
  elsif value.is_a?(String) && value.start_with?("http", "/")
    value
  end
rescue ArgumentError, URI::InvalidURIError
  nil
end

Instance Method Details

#view_templateObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/plutonium/ui/avatar.rb', line 87

def view_template
  url = resolved_src || navii_url

  if url
    img(
      src: url, alt: alt_text.to_s, width: pixel_size, height: pixel_size, loading: "lazy",
      **sized_attributes("rounded-full object-cover bg-[var(--pu-surface-alt)] shrink-0")
    )
  else
    div(**sized_attributes("rounded-full bg-[var(--pu-surface-alt)] text-[var(--pu-text-muted)] flex items-center justify-center shrink-0")) do
      render Phlex::TablerIcons::User.new(class: "w-2/3 h-2/3")
    end
  end
end