Class: Crm::PanelComponent

Inherits:
ApplicationComponent show all
Includes:
ActionView::Helpers::TagHelper, ActionView::Helpers::UrlHelper
Defined in:
app/components/crm/panel_component.rb

Overview

The single renderer behind every CRM panel: a Bootstrap card with an optional
icon + title header, optional header actions, and a body that can collapse.

Views don't instantiate this directly — CrmHelper#panel (collapsible) and
CrmHelper#simple_panel (static) are the entry points, and they translate
their legacy option hashes into these kwargs. That keeps ~1000 existing call
sites working while every panel in the CRM shares one piece of markup.

Styling lives in +client/stylesheets/crm/components/_panels.scss+; see
DESIGN.crm.md "Components" for the contract it implements.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationComponent

#cms_link, #fetch_or_fallback, #image_asset_tag, #image_tag, #number_to_currency, #number_with_delimiter, #post_path, #post_url, #strip_tags

Constructor Details

#initialize(title: nil, icon: nil, collapsible: false, open: true, id: nil, anchor_name: nil, card_class: nil, header_class: nil, header_outer_class: nil, title_class: nil, body_class: nil, wrap_title: true, wrap_body: true, **html) ⇒ PanelComponent

rubocop:disable Metrics/ParameterLists -- presentational knobs, all optional

Parameters:

  • title (String, nil) (defaults to: nil)

    header text (may be pre-built HTML); nil renders a header-less card

  • icon (String, Symbol, nil) (defaults to: nil)

    Font Awesome name rendered before the title

  • collapsible (Boolean) (defaults to: false)

    wrap the body in a Bootstrap collapse toggled by the title

  • open (Boolean) (defaults to: true)

    initial collapse state

  • id (String, nil) (defaults to: nil)

    card DOM id; generated when omitted

  • anchor_name (String, nil) (defaults to: nil)

    renders the title inside a named anchor

  • card_class (Array, String, nil) (defaults to: nil)

    extra classes on the +.card+

  • header_class (Array, String, nil) (defaults to: nil)

    extra classes on the +.card-header+

  • header_outer_class (String, nil) (defaults to: nil)

    replaces the +.card-header+ classes wholesale

  • title_class (Array, String, nil) (defaults to: nil)

    extra classes on the title element

  • body_class (Array, String, nil) (defaults to: nil)

    extra classes on the +.card-body+

  • wrap_title (Boolean) (defaults to: true)

    false renders the title without the standard span/icon wrapper

  • wrap_body (Boolean) (defaults to: true)

    false drops the +.card-body+ wrapper class

  • html (Hash)

    leftover HTML attributes, merged onto the +.card+

Options Hash (**html):

  • :class (String)

    extra card classes (composed with +card_class+)

  • :id (String)

    card id, when not given through +id:+



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'app/components/crm/panel_component.rb', line 40

def initialize(title: nil, icon: nil, collapsible: false, open: true, id: nil,
               anchor_name: nil, card_class: nil, header_class: nil, header_outer_class: nil,
               title_class: nil, body_class: nil, wrap_title: true, wrap_body: true, **html)
  @title = title
  @icon = icon
  @collapsible = collapsible
  @open = open != false
  @anchor_name = anchor_name
  @card_class = card_class
  @header_class = header_class
  @header_outer_class = header_outer_class
  @title_class = title_class
  @body_class = body_class
  @wrap_title = wrap_title
  @wrap_body = wrap_body
  @html = html.except(:class, :id)
  @id = (id.presence || html[:id].presence || "card-#{SecureRandom.hex(10)}").to_s
  @html_class = html[:class]
end

Instance Attribute Details

#idObject (readonly)

rubocop:enable Metrics/ParameterLists



61
62
63
# File 'app/components/crm/panel_component.rb', line 61

def id
  @id
end

#titleObject (readonly)

rubocop:enable Metrics/ParameterLists



61
62
63
# File 'app/components/crm/panel_component.rb', line 61

def title
  @title
end

Instance Method Details

#body_classesString

Class list for the body element, including the Bootstrap collapse
classes when the panel is collapsible.

Returns:

  • (String)


97
98
99
100
# File 'app/components/crm/panel_component.rb', line 97

def body_classes
  class_names(('card-body' if @wrap_body), 'crm-panel__body', @body_class,
    { 'collapse' => @collapsible, 'show' => @collapsible && @open })
end

#body_idString

The collapse target. dashboards/_panel.html.erb reconstructs this id to
decide which widgets reopen after a Turbo Stream replace — keep both in step.

Returns:

  • (String)


73
# File 'app/components/crm/panel_component.rb', line 73

def body_id = "body-#{id}"

#card_attributesHash{Symbol => Object}

HTML attributes for the card element: leftover +html+ kwargs plus the
computed id and class list.

Returns:

  • (Hash{Symbol => Object})


79
80
81
# File 'app/components/crm/panel_component.rb', line 79

def card_attributes
  @html.merge(id:, class: class_names('crm-panel card w-100', @card_class, @html_class))
end

#decorate_title?Boolean

A title built by the caller (dashboard widgets) supplies its own layout, so
skip the icon and the collapse caret rather than fighting it.

Returns:

  • (Boolean)


125
# File 'app/components/crm/panel_component.rb', line 125

def decorate_title? = @wrap_title

#header?Boolean

Whether the card renders a header: either a title was given or the
actions slot is populated.

Returns:

  • (Boolean)


67
# File 'app/components/crm/panel_component.rb', line 67

def header? = title.present? || actions?

#header_classesString

Class list for the +.card-header+ element; +header_outer_class+ replaces
the defaults entirely when given.

Returns:

  • (String)


87
88
89
90
91
# File 'app/components/crm/panel_component.rb', line 87

def header_classes
  @header_outer_class ||
    class_names('card-header crm-panel__header d-flex justify-content-between align-items-center',
      @header_class)
end

#title_classesString

Class list for the title element; carries +collapsed+ when a collapsible
panel starts closed so the caret renders correctly.

Returns:

  • (String)


119
120
121
# File 'app/components/crm/panel_component.rb', line 119

def title_classes
  class_names('card-title crm-panel__title', @title_class, 'collapsed' => @collapsible && !@open)
end

#title_tag { ... } ⇒ String

The collapsible variant is an toggling the body; the static one a plain
span. Both carry .card-title so existing per-page CSS keeps matching.

Yields:

  • the title content to capture into the tag

Returns:

  • (String)

    rendered HTML tag



107
108
109
110
111
112
113
# File 'app/components/crm/panel_component.rb', line 107

def title_tag(&)
  return tag.span(capture(&), class: title_classes) unless @collapsible

  link_to("##{body_id}", class: title_classes, role: 'button',
    data: { 'bs-toggle': 'collapse', 'bs-target': "##{body_id}" },
    aria: { expanded: @open, controls: body_id }, &)
end