Module: Layered::Ui::TableHelper

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

Instance Method Summary collapse

Instance Method Details

#l_ui_format_datetime(value) ⇒ Object

Formats a date/time value for display in a table cell.

l_ui_format_datetime(record.created_at) # => "15 Apr 2026, 10:30"


7
8
9
# File 'app/helpers/layered/ui/table_helper.rb', line 7

def l_ui_format_datetime(value)
  value&.strftime("%-d %b %Y, %H:%M")
end

#l_ui_table(records, columns:, caption: nil, actions: nil, actions_label: "Actions", query: nil, url: nil, turbo_frame: nil, row_id: 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. Every column must supply a render: proc that receives a record and returns cell content - the helper does not extract or format data itself.

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)  Used for label generation and sort links.
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)    Required. Receives (record), returns cell content.

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

Each <tr> is given id: dom_id(record) when the record responds to to_key (i.e. ActiveRecord), so individual rows can be targeted by Turbo Streams. Pass row_id: as a proc to override (return nil to omit the id).



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
72
73
74
75
76
77
78
79
80
# File 'app/helpers/layered/ui/table_helper.rb', line 41

def l_ui_table(records, columns:, caption: nil, actions: nil, actions_label: "Actions", query: nil, url: nil, turbo_frame: nil, row_id: 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(id: table_row_id(record, row_id)) 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")
end