Goal of LoopOS UI: standardize UX design, simplify DX
git branches (UI, Manager, Core): :branch: feature/loopos-ui-components
feature/loopos-ui-components
References: https://primer.style/components/ https://github.com/avo-hq/avo
<%= render LooposUi::IndexLayout.new do |layout| %> <% layout.with_action_bar do |action_bar| %> <% action_bar.with_action_buttons do |action_buttons| %> <% action_buttons.with_button_group do |bg| %> <% bg.with_button_new(href: ... )%> <% bg.with_button_export(text: "...", href: ... )%> <% bg.with_button_import(text: "...", href: ... )%> <% end %> <% end %> <% end %> <% layout.with_header(title: "...") do |header| %> <% header.with_title_label_counter(text: "#{@model.count}") %> <% if current_user.can_do_something? %> <% header.with_title_label_counter(text: "", icon: "fa-regular fa-circle-exclamation") %> <% end %> <% end %> <%# CONTENT %> <% end %>
Automatic breadcrumbs Infer configurations from model class Can always override Less boilerplate, more consistency
<%= render LooposUi::IndexLayout.new(model_class: Cluster) do |layout| %> <% layout.with_action_bar %> <% layout.with_header %> <%= render "admin/clusters/index-table", method: :post %> <% end %>
<%= render LooposUi::ShowLayout.new do |layout| %> <% layout.with_action_bar do |action_bar| %> <% action_bar.with_breadcrumbs_list do |breadcrumbs| %> <% breadcrumbs.with_breadcrumb(...) %> <% end %> <% action_bar.with_action_buttons do |buttons| %> <% buttons.with_button_group do |button_group| %> <% button_group.with_button(...) %> <% end %> <% buttons.with_button_group.with_button_back(...) %> <% end %> <% end %> <% layout.with_header(title: ...) do |header| %> <% header.with_token(...) do %> <% header.with_details do %> ... <% end %> <% end %> <%# CONTENT %> <% end %>
easy to find and edit components HTML, JS and CSS
app/ ├─ assets/javascript/controllers/ │ └─ view_component_controller.js │ └─ view_components/ ├─ view_component.rb └─ view_components/ ├─ view_component.html.erb ├─ view_component.css ├─ inner_component.rb └─ inner_component.css
preview, documentation and (eventually) tests
test/components/ ├─ previews/components/navigation/ #reflects folder structure in Lookbook │ ├─ button_preview.rb │ └─ button_preview/all.html.erb └─ loopos_ui/documentation/button.md
A ViewComponent is a Ruby class that acts as a small controller for a part of a view. It is a way to extract reusable components from views.
Slotting is the "equivalent" to partial's yield, but better. Learn more at https://viewcomponent.org/
yield
https://dry-rb.org/gems/dry-initializer/3.0/
# Before class SomeComponent < View::Component attr_reader :foo, :bar, :baz def initialize(foo: nil, bar: 1, baz: "default") @foo = foo @bar = bar @baz = baz end end
# After class SomeComponent < View::Component option :foo, optional: true option :bar, default: -> { 1 } option :baz, default: -> { "default" } end
# app/lib/loopos_ui/button/presets.rb # Can use factory method LooposUi::Button.for_enable(...) def for_enable(**args) new( leading_icon: "fa-regular fa-play-pause", type: :secondary, kind: :neutral, **args, ) end #... # Can use in polymorphic slots like `with_button_enable(...)` def presets [{ enable: { renders: method(:for_enable),}, }, ...] end
A resource acts like a bridge between the model and the LoopOS Components. Some components are ResourceAware and can infer the Resource from the model/model class and use it to prefill slots automatically.
ResourceAware
Resource
LoopComponents -> can use a Resource -> can use Model/Model Class
LoopComponents
# app/components/loopos_ui/index_layout.rb, resource aware component def before_render if resource.present? && with_counter? with_title_label_counter( text: "#{model_class.count} #{resource.model_class_plural_name.downcase}" ) end end private def with_counter? resource&.with_counter || @with_counter end # items/index.html.erb <%= render LooposUi::IndexLayout.new(model_class: Item) do |layout| %> ... # resoureces/item_resource.rb class ItemResource < LooposUi::Resource self.title = :full_id self.use_counter = true end
Questions
30/35 min presentation 3:30 - 5 develop 1h discussion
├─ │