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", floating_actions: false, 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), class: "l-ui-button l-ui-button--outline l-ui-button--small" },
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 floating_actions: true to pin the actions column to the right-hand edge of the scroll container, so row actions stay visible while the table scrolls horizontally.

The table is wrapped in an l-ui-scroll-hint element wired to the l-ui--scroll-hint controller, which fades the clipped edge while more columns are available in that direction.

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

Each 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).



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
81
82
83
84
85
86
87
88
89
90
91
# File 'app/helpers/layered/ui/table_helper.rb', line 49

def l_ui_table(records, columns:, caption: nil, actions: nil, actions_label: "Actions", floating_actions: false, 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 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 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_classes = ["l-ui-table"]
  table_classes << "l-ui-table--floating-actions" if actions && floating_actions
  table = tag.table(class: table_classes.join(" ")) { safe_join([caption_tag, thead, tbody].compact) }
  container = tag.div(table, class: "l-ui-table-container", data: { "l-ui--scroll-hint-target" => "scroller" })
  tag.div(container, class: "l-ui-scroll-hint", data: { controller: "l-ui--scroll-hint" })
end