Module: BrutalistRailsUi::Helpers

Defined in:
lib/brutalist_rails_ui/helpers.rb

Instance Method Summary collapse

Instance Method Details

#card(title = nil, &block) ⇒ Object

White card with optional black header bar.

<%= card "Recent Transactions" do %>
  <p class="p-6">Content here</p>
<% end %>


40
41
42
43
44
45
46
47
48
49
# File 'lib/brutalist_rails_ui/helpers.rb', line 40

def card(title = nil, &block)
   :div, class: "bg-white border-2 border-black overflow-hidden" do
    if title
      concat (:div, class: "px-6 py-3 bg-black") {
        (:h2, title, class: "text-xs font-bold text-white uppercase tracking-wider")
      }
    end
    concat capture(&block)
  end
end

#card_header(title, action_text: nil, action_path: nil) ⇒ Object

Card header bar with optional right-side action.

<%= card_header "Accounts", action_text: "View all", action_path: accounts_path %>


54
55
56
57
58
59
60
61
# File 'lib/brutalist_rails_ui/helpers.rb', line 54

def card_header(title, action_text: nil, action_path: nil)
   :div, class: "px-6 py-3 bg-black flex items-center justify-between" do
    concat (:h2, title, class: "text-xs font-bold text-white uppercase tracking-wider")
    if action_text && action_path
      concat link_to(action_text, action_path, class: "text-xs text-yellow-400 hover:text-white font-bold")
    end
  end
end

#cta_banner(headline:, subtext: nil, link_text:, path:, **link_options) ⇒ Object

Yellow CTA banner with headline and action link.

<%= cta_banner headline: "Connect a bank",
               subtext: "Sync transactions automatically",
               link_text: "Get Started",
               path: banks_link_path %>


99
100
101
102
103
104
105
106
107
# File 'lib/brutalist_rails_ui/helpers.rb', line 99

def cta_banner(headline:, subtext: nil, link_text:, path:, **link_options)
   :div, class: "border-2 border-black bg-yellow-400 p-6 flex items-center justify-between gap-4" do
    concat (:div) {
      concat (:p, headline, class: "font-black text-black uppercase")
      concat (:p, subtext, class: "text-sm text-black mt-1 font-bold") if subtext
    }
    concat link_to(link_text, path, class: "btn-primary flex-shrink-0", **link_options)
  end
end

#empty_state(icon: nil, message:, &block) ⇒ Object

Centered empty state with icon and message.

<%= empty_state icon: "✓", message: "All transactions assigned" %>
<%= empty_state icon: "📭", message: "No accounts yet" do %>
  <%= link_to "Connect a bank", banks_link_path, class: "btn-primary mt-4" %>
<% end %>


69
70
71
72
73
74
75
# File 'lib/brutalist_rails_ui/helpers.rb', line 69

def empty_state(icon: nil, message:, &block)
   :div, class: "px-6 py-12 text-center" do
    concat (:p, icon, class: "text-3xl mb-3") if icon
    concat (:p, message, class: "font-black text-black uppercase tracking-wide")
    concat capture(&block) if block
  end
end

#kpi_box(label:, value:, dark: false) ⇒ Object

KPI stat box for summary grids.

<div class="grid grid-cols-2 gap-4">
  <%= kpi_box label: "Total Spent", value: "$12.40" %>
  <%= kpi_box label: "Total Calls", value: "42", dark: true %>
</div>


83
84
85
86
87
88
89
90
91
# File 'lib/brutalist_rails_ui/helpers.rb', line 83

def kpi_box(label:, value:, dark: false)
  bg = dark ? "bg-black text-white" : "bg-white border-2 border-black text-black"
  label_class = dark ? "text-xs font-bold uppercase tracking-widest text-gray-400 mb-1" \
                     : "text-xs font-bold uppercase tracking-widest text-gray-500 mb-1"
   :div, class: "#{bg} p-5" do
    concat (:p, label, class: label_class)
    concat (:p, value, class: "text-2xl font-black")
  end
end

#money(amount) ⇒ Object

Format a number as currency.



129
130
131
# File 'lib/brutalist_rails_ui/helpers.rb', line 129

def money(amount)
  number_to_currency(amount, unit: "$", precision: 2)
end

#money_class(amount) ⇒ Object

CSS class for positive/negative amounts.



134
135
136
# File 'lib/brutalist_rails_ui/helpers.rb', line 134

def money_class(amount)
  amount.to_f.negative? ? "text-red-600" : "text-black"
end

Nav link that highlights when active. Pass mobile: true for the vertical mobile menu variant.



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/brutalist_rails_ui/helpers.rb', line 5

def nav_link(label, path, mobile: false, **options)
  base = path.split("?").first
  active = current_page?(path) || (base != "/" && request.path.start_with?(base))
  if mobile
    classes = active \
      ? "block bg-yellow-400 text-black px-4 py-3 text-sm font-bold border-b border-gray-700" \
      : "block text-white hover:bg-gray-800 px-4 py-3 text-sm font-bold border-b border-gray-700"
  else
    classes = active \
      ? "bg-yellow-400 text-black px-3 py-4 text-sm font-bold border-r border-l border-yellow-400" \
      : "text-white hover:bg-white hover:text-black px-3 py-4 text-sm font-bold border-r border-gray-700 last:border-r-0"
  end
  link_to label, path, class: classes, **options
end

#page_header(title, subtitle: nil, &block) ⇒ Object

Page header row: h1 title + optional action buttons block.

<%= page_header "Transactions" do %>
  <%= link_to "+ Add", new_transaction_path, class: "btn-primary" %>
<% end %>


25
26
27
28
29
30
31
32
33
# File 'lib/brutalist_rails_ui/helpers.rb', line 25

def page_header(title, subtitle: nil, &block)
   :div, class: "flex flex-wrap items-center justify-between gap-3 mb-6" do
    concat (:div) {
      concat (:h1, title, class: "text-2xl font-black text-black uppercase tracking-tight")
      concat (:p, subtitle, class: "text-sm font-bold text-gray-500 mt-1 uppercase tracking-wide") if subtitle
    }
    concat (:div, capture(&block), class: "flex flex-wrap items-center gap-2") if block
  end
end

#status_badge(status, color_map = {}) ⇒ Object

Status badge pill.

<%= status_badge "pending" %>
<%= status_badge "funded", "funded" => "bg-green-600 text-white" %>


113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/brutalist_rails_ui/helpers.rb', line 113

def status_badge(status, color_map = {})
  defaults = {
    "active"    => "bg-black text-white",
    "funded"    => "bg-black text-white",
    "over"      => "bg-red-600 text-white",
    "pending"   => "bg-yellow-400 text-black",
    "income"    => "bg-black text-white",
    "transfer"  => "bg-white text-black",
    "untracked" => "bg-white text-black"
  }.merge(color_map)
  color = defaults[status.to_s] || "bg-white text-black"
  tag.span status.to_s.humanize,
    class: "inline-flex items-center border-2 border-black px-2 py-0.5 text-xs font-bold uppercase tracking-wide #{color}"
end