Module: Layered::Ui::TableHelper

Defined in:
app/helpers/layered/ui/table_helper.rb

Instance Method Summary collapse

Instance Method Details

#l_ui_table(records, columns:, caption: nil, actions: nil, actions_label: "Actions", query: nil, url: nil, turbo_frame: nil) ⇒ Object

Renders a styled, accessible data table.

Use this helper in any view to render a table with the engine’s l-ui-table styles. It supports custom cell rendering via procs, an optional actions column, and Ransack sort links.

l_ui_table(@personas,
  columns: [
    { attribute: :name, primary: true, render: ->(r) { link_to r.name, persona_path(r) } },
    { attribute: :description, render: ->(r) { truncate(r.description, length: 60) } },
  ],
  actions: ->(r) { link_to "Edit", edit_persona_path(r) },
  caption: "Personas"
)

Column options:

attribute: (Symbol)  Model attribute for data and label generation.
label:     (String)  Custom header text. Defaults to humanised attribute.
primary:   (Boolean) Renders as <th scope="row">. Defaults to first column.
sortable:  (Boolean) Show sort link when query: is provided. Defaults to true.
render:    (Proc)    Receives (record), returns cell content.

When no render: proc is given, the cell value is extracted via record[attribute] for hashes or record.public_send(attribute) for objects, with automatic date formatting.

Pass query: (a Ransack search object) and turbo_frame: to enable sortable column headers via l_ui_sort_link.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/helpers/layered/ui/table_helper.rb', line 32

def l_ui_table(records, columns:, caption: nil, actions: nil, actions_label: "Actions", query: nil, url: nil, turbo_frame: nil)
  columns = normalise_table_columns(columns)
  col_count = columns.size + (actions ? 1 : 0)

  thead = tag.thead(class: "l-ui-table__header") do
    tag.tr do
      cells = columns.map do |col|
        if col[:sortable] && query
          l_ui_sort_link(query, col[:attribute], col[:label], url: url, turbo_frame: turbo_frame)
        else
          tag.th(col[:label], class: "l-ui-table__header-cell", scope: "col")
        end
      end
      cells << tag.th(actions_label, class: "l-ui-table__header-cell--action", scope: "col") if actions
      safe_join(cells)
    end
  end

  tbody = tag.tbody(class: "l-ui-table__body") do
    if records.empty?
      tag.tr do
        tag.td("No records found.", class: "l-ui-table__cell", colspan: col_count)
      end
    else
      safe_join(records.map do |record|
        tag.tr do
          cells = columns.map do |col|
            table_cell(record, col)
          end
          cells << tag.td(class: "l-ui-table__cell--action") { actions.call(record) } if actions
          safe_join(cells)
        end
      end)
    end
  end

  caption_tag = caption ? tag.caption(caption, class: "l-ui-sr-only") : nil
  table = tag.table(class: "l-ui-table") { safe_join([caption_tag, thead, tbody].compact) }
  tag.div(table, class: "l-ui-container--table l-ui-utility--mt-lg")
end