Class: Plutonium::UI::Component::Section

Inherits:
Base
  • Object
show all
Defined in:
lib/plutonium/ui/component/section.rb

Overview

Shared chrome for a declared layout section — a card with a header row (accent bar, heading, optional description, optional collapse caret) over a body that holds the fields. Subclassed (not copied) by both Form::Components::Section and Display::Components::Section so a form section and a show-page section always read the same; the only difference between the two is which grid class the caller passes in.

Each section is its OWN card rather than a band inside one shared card. The card boundary does the grouping — no amount of heading typography separates two field groups as unambiguously as an actual edge does, and the header row then only has to name the group rather than carry the whole structural signal. Callers therefore must NOT wrap sections in a card of their own; they supply a plain stacking container.

The caller supplies the fields as a block and owns what a "field" means.

Constant Summary collapse

DEFAULT_THEME =

Default section chrome, merged into BOTH Form::Theme and Display::Theme so the two read identically out of the box while each stays independently overridable — the same contract every other themed key has. Defined here rather than written out twice so the shipped defaults cannot drift between the form and the show page.

Structure (which element gets which key, the group/[open] mechanics) stays in this component; themes own the classes.

{
  # Merged into the section's {Plutonium::UI::Block} card — Block
  # supplies `pu-card` itself, so this only adds what is specific to a
  # section. `overflow-hidden` keeps the header row's fill and border
  # inside the card's rounded corners.
  section_wrapper: "overflow-hidden",

  # Header row. Its bottom border is the line between the header and
  # the fields — the card edge already separates one section from the
  # next, so nothing else has to.
  section_header:
    "px-4 py-3 flex items-center gap-3 " \
    "border-b border-[var(--pu-border)] bg-[var(--pu-surface-alt)]",

  # A short primary bar at the head of the row. A standalone element
  # rather than a border on the text block, so the heading and its
  # description stay vertically centred against it.
  section_accent: "shrink-0 w-1 h-5 rounded-full bg-primary-500",

  # Sized to sit under the page title (text-xl semibold) while
  # out-ranking display values (text-lg regular) and field labels.
  # With the card doing the separating, the heading no longer has to
  # shout to be read as a header.
  section_heading: "text-base font-bold tracking-tight text-[var(--pu-text)]",
  section_description: "text-sm font-normal text-[var(--pu-text-muted)]",

  # `list-none` + the WebKit marker reset remove the native disclosure
  # triangle, which the browser pins to the LEFT of the summary — where
  # it would displace the accent bar and knock the heading out of the
  # alignment every other section keeps. The caret is re-drawn on the
  # right instead, so headers stay identical whether or not a section
  # happens to be collapsible.
  # The divider only exists to separate the header from the fields, so
  # a COLLAPSED section must not draw one — there is nothing below it,
  # and the line would land directly on the card's own bottom border.
  # `group-open:` keys it to the parent <details>'s [open] state.
  section_summary:
    "px-4 py-3 flex items-center gap-3 cursor-pointer select-none " \
    "border-b-0 group-open:border-b border-[var(--pu-border)] " \
    "bg-[var(--pu-surface-alt)] " \
    "list-none [&::-webkit-details-marker]:hidden",

  section_caret:
    "shrink-0 w-3 h-3 text-[var(--pu-text-muted)] " \
    "transition-transform duration-200 group-open:rotate-180",

  # Padding box between the card edge and the field grid. Roomier than
  # the stock `pu-card-body` (16px): a section card holds inputs, not
  # the dense label/value rows a card body is sized for, and the form
  # this replaced was a single `p-8` card — at 16px the fields sit
  # noticeably tighter against the edge than they used to.
  section_body: "p-6"
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Behaviour

#around_template

Methods included from Tokens

#classes, #tokens

Methods included from 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(resolved, grid_class:) ⇒ Section

Returns a new instance of Section.



92
93
94
95
# File 'lib/plutonium/ui/component/section.rb', line 92

def initialize(resolved, grid_class:)
  @section = resolved.section
  @grid_class = grid_class
end

Dynamic Method Handling

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

Class Method Details

.theme_classObject

The theme this section's chrome resolves against. Subclasses name their own, so a form section follows Form::Theme and a show-page section follows Display::Theme.

Raises:

  • (NotImplementedError)


88
89
90
# File 'lib/plutonium/ui/component/section.rb', line 88

def self.theme_class
  raise NotImplementedError, "#{self} must implement .theme_class"
end

Instance Method Details

#view_template(&fields_block) ⇒ Object

Every section is a Block — the shared card primitive — so a section card and any other card on the page are the same surface by construction rather than by two lists of classes that happen to agree today.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/plutonium/ui/component/section.rb', line 101

def view_template(&fields_block)
  Block(class: themed_section(:section_wrapper)) do
    if @section.collapsible?
      # `group` lets the caret rotate off the <details>'s [open] state.
      details(open: !@section.collapsed?, class: "group") do
        # <summary> must be the first child of <details> and can't be
        # wrapped, so it IS the header row.
        summary(class: themed_section(:section_summary)) do
          span(class: themed_section(:section_accent))
          heading_block
          render_caret
        end
        body(&fields_block)
      end
    else
      header_row
      body(&fields_block)
    end
  end
end