matestack-ui-bootstrap
Boost your productivity & easily create reactive andstyled web UIs in pure Ruby.
matestack-ui-bootstrap ships all you need to build reactive AND styled UIs in pure Ruby orchestrating styled components (based on Bootstrap v5) with a simple Ruby DSL. Additionally smart CRUD components helps you building beautiful data-driven admin and application UIs.
All features of matestack-ui-core and matestack-ui-vuejs can be mixed in which gives you maximum flexibility while implementing your UIs.

Compatibility

matestack-ui-bootstrap requires matestack-ui-vuejs and matestack-ui-core
matestack-ui-bootstrap is tested against:
  • Rails 7.0.1 + Ruby 3.0.0 + Vue.js 3.2.26
  • Rails 6.1.1 + Ruby 3.0.0 + Vue.js 3.2.26
  • Rails 6.1.1 + Ruby 2.7.2 + Vue.js 3.2.26
  • Rails 6.0.3.4 + Ruby 2.6.6 + Vue.js 3.2.26
  • Rails 5.2.4.4 + Ruby 2.6.6 + Vue.js 3.2.26
Rails versions below 5.2 are not supported.
Vue.js 2.x is supported when using the Compat build of Vue.js

Documentation/Installation

Feature walk-through

You might want to have a look at the dummy app in oder to better understand what kind of results you get out of matestack-ui-bootstrap

All Bootstrap v5 components, available in pure Ruby

Alongside smart components, matestack-ui-boostrap ships all Bootstrap v5 components enabling you to use them in pure Ruby.
Imagine adding a Bootstrap `card` component within one line of Ruby, skipping recreating the required DOM structures again and again?
1
bs_card title: "foo", body: "bar" #bs_card is shipped within matestack-ui-bootstrap
Copied!
renders:
1
<div class="card">
2
<div class="card-body">
3
<h5 class="card-title">foo</h5>
4
<p class="card-text">bar</p>
5
</div>
6
</div>
Copied!
Mix that with Matestack's core components, Bootstrap's utility classes or custom CSS for customized UI implementation. That means you're able to use Bootstrap and matestack-ui-boostrap components with a high level of abstraction for maximum productivity right next to core components like `div` with a lower level of abstraction for maximum flexibility!
On top of that, you're able to use all kinds of methods in order to render your UI based on conditions like `current_user.is_super_admin?`. Adjusting the UI to your custom rules based on pure Ruby is super easy. That's what we call flexible abstraction!
The following code snippet demonstrates the usage of components shippend in matestack-ui-bootstrap (bs_*) alongside using utility classes (mt-3) and matestack-ui-core components (small,b ...) and reactive components from matestack-ui-vuejs (transition):
1
class MyAdmin::Pages::Customer::Index < Matestack::Ui::Page
2
3
def response
4
bs_container do
5
bs_row do
6
Customer.last(6).each do |customer|
7
bs_col sm: 4, class: "mt-3" do
8
card_for(customer)
9
end
10
end
11
end
12
end
13
end
14
15
def card_for customer
16
bs_card title: customer.name, img_path: asset_pack_url('.../xyz.png'), class: "shadow-sm" do
17
card_body_for(customer)
18
end
19
end
20
21
def card_body_for customer
22
small class: "mb-3" do
23
b text: "Email:"
24
plain customer.email
25
end
26
if current_user.is_super_admin?
27
transition path: form_path(id: customer.id) do
28
bs_btn size: :sm, text: "edit", class: "mt-3"
29
end
30
end
31
end
32
33
end
34
Copied!

Flexible, responsive, prebuilt app layouts

Use our prebuilt app templates through class inheritance in order to quickly setup typical layouts including sidebar and header navigation. Styles can be customized via SCSS theming.
Thanks to the fact that you're dealing with pure Ruby classes, it's also pretty easy to modify prebuilt UI structures and appearance in order to tailor the admin app to your individual needs.
1
class MyAdmin::Layout < Matestack::Ui::Bootstrap::Layouts::AdminTemplate
2
3
# the response method is defined by the parent class
4
# you just need to pass in some configuration using the methods below
5
# it's still possible to overwrite and adjust the response method
6
# defined in the parent class
7
8
def sidebar_top_partial
9
div class: "text-center" do
10
transition path: root_path, delay: 300 do
11
h4 "Your Rails Backend"
12
end
13
end
14
div class: "text-center my-5" do
15
bs_avatar img_path: asset_pack_url('media/images/avatar-placeholder.png')
16
div class: "my-3" do
17
plain current_admin.email
18
end
19
end
20
end
21
22
def sidebar_navigation_items
23
[
24
{ type: :transition, path: dummy_dashboard_path, text: "Dashboard", icon: "columns-gap" },
25
{ type: :transition, path: dummy_products_path, text: "Products", icon: "box" },
26
{ type: :transition, path: dummy_customers_path, text: "Customers", icon: "people-fill" },
27
{ type: :transition, path: dummy_orders_path, text: "Orders", icon: "cart-check-fill"},
28
]
29
end
30
31
def toasts
32
[
33
{ show_on: "failure", class: "bg-danger text-white", body: "Error!" },
34
{ show_on: "success", class: "bg-primary text-white", body: "Success!" },
35
]
36
end
Copied!
leading to something like this (layout with sidebar and toasts tiggered on form and action submissions, page content is obviously defined somewhere else ;) -->

Powerful page layout components for great UI experience

Use components like bs_page_heading or bs_section_card together with grid components like bs_row and bs_col in order to quickly create a well structured, consistent and good looking UI.
Split rendering of complex, data-intesive UIs with Matestack's VueJs async component and increase initial page load performance! All without writing one line of JavaScript:
1
class MyAdmin::Pages::Dashboard::Show < Matestack::Ui::Page
2
3
def response
4
bs_page_heading title: t("dashboard.title"), subtitle: t("dashboard.subtitle")
5
bs_row do
6
bs_col md: 6 do
7
analytics_partial
8
end
9
bs_col md: 6 do
10
activity_partial
11
end
12
end
13
end
14
15
def analytics_partial
16
async defer: 300, id: "products-card" do
17
MyAdmin::Components::Dashboard::Products.call()
18
end
19
async defer: 600, id: "revenue-card" do
20
MyAdmin::Components::Dashboard::Revenue.call()
21
end
22
end
23
24
def activity_partial
25
bs_row do
26
bs_col do
27
async defer: 900, rerender_on: "activity-tracked", id: "activity-card" do
28
MyAdmin::Components::Dashboard::Activity.call()
29
end
30
end
31
end
32
end
33
34
end
Copied!
and e.g.:
1
class MyAdmin::Components::Dashboard::Revenue < Matestack::Ui::Component
2
3
def response
4
section_card title: t("dashboard.revenue.title"), subtitle: t("dashboard.revenue.subtitle") do
5
row do
6
col xl: 6 do
7
text_kpis_partial
8
end
9
col xl: 6, class: "py-3" do
10
chart_kpis_partial
11
end
12
end
13
end
14
end
15
16
# ...
17
18
end
Copied!
leading to something like this -->

Chart.js components accessible in pure Ruby

Want to visualize some data in charts? Matestack UI Bootstrap let's you easily integrate chart.js (copy/paste once from example in documentation [or choose any other chart library]) which from then on allows you to use chart components in pure Ruby without thinking of the JavaScript side!
Choose from line, doughnut, bar or pie charts and use theme colors for consistent coloring of datasets without touching CSS:
1
class MyAdmin::Components::Dashboard::Products < Matestack::Ui::Component
2
3
def response
4
bs_section_card title: t("..."), subtitle: t("...") do
5
bs_row do
6
bs_col xl: 6 do
7
text_kpis_partial
8
end
9
bs_col xl: 6, class: "py-3" do
10
chart_kpis_partial
11
end
12
end
13
end
14
end
15
16
protected
17
18
# ...
19
20
def chart_kpis_partial
21
# Component available for copy/paste in matestack-ui-bootstrap docs!
22
Components::ChartJs type: :doughnut,
23
datasets: [
24
{
25
data: top_5_product_values,
26
backgroundColor: [:primary, :secondary, :blue, :indigo, :info]
27
},
28
],
29
labels: top_5_product_names
30
end
31
32
def top_5_products
33
OrderItem
34
.group(:product_id)
35
.sum(:price_in_euro)
36
.sort_by{|k, v| v}
37
.reverse
38
.first(5)
39
end
40
41
def top_5_product_values
42
top_5_products
43
.map { |product_id, value| value }
44
end
45
46
def top_5_product_names
47
top_5_products
48
.map { |product_id, value| Product.find(product_id).name }
49
end
50
51
end
Copied!
which leads to something like this -->

Reactive and styled forms in no time

matestack-ui-vuejs already ships reactive forms, usable with pure Ruby. Within matestack-ui-bootstrap you get styled form components, enabling you to create beautiful, reactive forms with a few lines of Ruby!
Create styled forms, with reactive error/success rendering without thinking of any implementation detail:
1
class MyAdmin::Components::UsersForm < Matestack::Ui::Component
2
3
required :user
4
5
def response
6
matestack_form form_config do
7
bs_form_input key: :name, type: :text, label: "Name"
8
bs_form_input key: :avatar, type: :file, label: "Avatar"
9
bs_form_select key: :role, options: [:client, :admin], label: "Role",
10
placeholder: "Select Role"
11
bs_form_switch key: :active, label: "Active?"
12
bs_form_submit button_variant: :primary, spinner_variant: :light, text: "Submit"
13
end
14
end
15
16
def form_config
17
{
18
multipart: true, # due to file upload
19
for: context.user,
20
path: admin_user_path(context.user.id),
21
method: :put,
22
success: {
23
emit: :success
24
},
25
failure: {
26
emit: :failure
27
}
28
}
29
end
30
31
32
end
Copied!
which results in something like this --> (errors are rendered dynamically after async form submission, a error toast would trigger as well if added to the layout)

Reactive, paginated, filterable tables, without the JavaScript hussle

Implementing a paginated, filterable collection is exhausting. And what about some reactivity when switching through the pages in order to avoid full page reloads? You don't want to build that yourself! That's why we've created the collection component, shipped within matestack-ui-core. The bs_smart_collection shipped with matestack-ui-bootstrap gives you even more:
A few lines of Ruby is enough to add a styled, reactive paginated table with filters to your UI! You can optionally modify column rendering and per-row actions via method injection:
1
class MyAdmin::Pages::Orders::Index < Matestack::Ui::Page
2
3
def response
4
# ...
5
bs_section_card do
6
bs_smart_collection collection_config
7
end
8
# ...
9
end
10
11
def collection_config
12
{
13
id: 'orders',
14
# Active Record query:
15
items: Order.includes(:customer, :order_items).all,
16
paginate: 10,
17
rerender_on: "success",
18
columns: {
19
# just render the ID:
20
id: 'ID',
21
# render an attribute of a child model:
22
'customer.last_name': {
23
# use a specific table column heading:
24
heading: 'Customer Name'
25
},
26
price_in_euro: {
27
heading: 'Price in €',
28
# transform the column content with a Proc:
29
format: -> (column_data){ "#{column_data} €" },
30
text: :right
31
}
32
},
33
filters: {
34
'customer.last_name': {
35
placeholder: 'Filter by Customer Name',
36
match: :starts_with,
37
}
38
},
39
slots: {
40
# inject a method which defines the per row actions:
41
table_item_actions: method(:table_item_actions)
42
}
43
}
44
end
45
46
def table_item_actions order
47
transition path: edit_dummy_order_path(order), delay: 300 do
48
bs_btn outline: true, size: :sm, variant: :primary do
49
bs_icon name: 'arrow-right', size: 20
50
end
51
end
52
end
53
54
end
Copied!
which leads to something like this -->
...and if you're not into tables, you can adjust the collection content rendering with some custom ruby code while keeping all the reactivity:
1
class MyAdmin::Pages::Products::Index < Matestack::Ui::Page
2
3
# ...
4
5
def collection_config
6
{
7
id: 'products',
8
items: Product.all,
9
paginate: 9,
10
rerender_on: "success",
11
filters: {
12
name: {
13
placeholder: 'Filter by Name',
14
match: :like
15
},
16
},
17
slots: {
18
collection_rendering: method(:collection_rendering)
19
}
20
}
21
end
22
23
def collection_rendering products
24
slot do
25
bs_row do
26
products.each do |product|
27
bs_col xl: 4, class: "mb-3" do
28
collection_card product
29
end
30
end
31
end
32
end
33
end
34
35
def collection_card product
36
bs_card title: product.name, subtitle: "#{product.price_in_euro} €", class: "h-100" do
37
paragraph product.description, class: "fw-lighter"
38
transition path: edit_dummy_product_path(product), delay: 300 do
39
bs_btn outline: true, size: :sm, variant: :primary do
40
bs_icon name: 'arrow-right', size: 20
41
end
42
end
43
action product_delete_action_config(product) do
44
bs_btn outline: true, size: :sm, variant: :danger do
45
bs_icon name: 'trash2', size: 20
46
end
47
end
48
end
49
end
50
51
def product_delete_action_config product
52
{
53
method: :delete,
54
path: dummy_product_path(id: product.id),
55
confirm: true,
56
success: {
57
emit: "success"
58
}
59
}
60
end
61
62
63
end
64
Copied!
which then looks like this -->
Get started now --> Detailed documentation can be found here