Class: RubyUIConverter::ComponentMap

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_ui_converter/component_map.rb

Overview

Maps HTML elements onto RubyUI (or any Phlex) components.

Two layers of rules:

* user rules, added with #register — always win;
* fallback rules — the built-in RubyUI element mapping installed by
  Configuration when `ruby_ui` is enabled (the default).

Each rule has:

* matcher: ->(Nodes::Element) { Boolean }
* emitter: ->(Nodes::Element, Transformer, CodeBuilder) { ... }

The emitter is responsible for writing the component call. It can use the transformer’s public helpers (kit_component, wrap_component, emit_children, render_attrs, meaningful).

Example of a custom rule (overrides the built-in ‘button` mapping):

config.component_map.register(
  ->(el) { el.name == "button" && el.static_classes.include?("danger") }
) do |el, transformer, builder|
  transformer.kit_component("Button", el, builder, extra: "variant: :destructive")
end

Defined Under Namespace

Classes: Rule

Constant Summary collapse

ELEMENT_COMPONENTS =

Element name -> [RubyUI kit component, void?] for 1:1 mappings.

{
  "button" => ["Button", false],
  "textarea" => ["Textarea", false],
  "select" => ["NativeSelect", false],
  "option" => ["NativeSelectOption", false],
  "table" => ["Table", false],
  "thead" => ["TableHeader", false],
  "tbody" => ["TableBody", false],
  "tfoot" => ["TableFooter", false],
  "tr" => ["TableRow", false],
  "th" => ["TableHead", false],
  "td" => ["TableCell", false],
  "caption" => ["TableCaption", false],
  "hr" => ["Separator", true]
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeComponentMap

Returns a new instance of ComponentMap.



46
47
48
49
# File 'lib/ruby_ui_converter/component_map.rb', line 46

def initialize
  @rules = []
  @fallback_rules = []
end

Class Method Details

.rubyui_rules(map) ⇒ Object

Built-in mapping of basic HTML elements onto RubyUI kit components (Link, Button, Input, …). Installed as fallback rules so user rules registered with #register always win.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ruby_ui_converter/component_map.rb', line 76

def self.rubyui_rules(map)
  # <a href=...> -> Link(href: ...). Anchors without href stay plain.
  map.register_fallback(->(el) { el.name == "a" && el.attr?("href") }) do |el, t, b|
    t.kit_component("Link", el, b)
  end

  # <input> -> Checkbox / RadioButton / Input, dispatched on a static
  # type attribute (Checkbox and RadioButton set their own type).
  map.register_fallback(->(el) { el.name == "input" }) do |el, t, b|
    case el.static_attr("type")
    when "checkbox" then t.kit_component("Checkbox", el, b, except: ["type"], void: true)
    when "radio" then t.kit_component("RadioButton", el, b, except: ["type"], void: true)
    else t.kit_component("Input", el, b, void: true)
    end
  end

  ELEMENT_COMPONENTS.each do |element, (component, void)|
    map.register_fallback(->(el) { el.name == element }) do |el, t, b|
      t.kit_component(component, el, b, void: void)
    end
  end

  # Rails flash paragraphs (<p id="notice">/<p id="alert">) -> RubyUI Alert.
  # `notice` is a success message; `alert` is an error (destructive).
  map.register_fallback(->(el) { el.name == "p" && %w[notice alert].include?(el.static_attr("id")) }) do |el, t, b|
    kind = el.static_attr("id")
    variant = kind == "alert" ? "destructive" : "success"
    # mb-5 keeps the alert from sitting flush against the content below it
    # (the scaffold's flash <p> had mb-5, dropped when we replace the tag).
    b.line(%(Alert(variant: :#{variant}, class: "mb-5") do))
    b.indent
    b.line(%(AlertTitle { "#{kind.capitalize}" }))
    t.component_block("AlertDescription", el.children, b)
    b.dedent
    b.line("end")
  end

  # Class-based heuristics for common Bootstrap-ish markup.
  map.register_fallback(->(el) { el.static_classes.include?("badge") }) do |el, t, b|
    t.kit_component("Badge", el, b)
  end

  map.register_fallback(->(el) { el.static_classes.include?("card") }) do |el, t, b|
    t.kit_component("Card", el, b)
  end

  map
end

Instance Method Details

#empty?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/ruby_ui_converter/component_map.rb', line 69

def empty?
  @rules.empty? && @fallback_rules.empty?
end

#lookup(node) ⇒ Proc?

User rules take precedence over the built-in fallback rules.

Returns:

  • (Proc, nil)

    the emitter for the first matching rule.



63
64
65
66
67
# File 'lib/ruby_ui_converter/component_map.rb', line 63

def lookup(node)
  rule = @rules.find { |r| r.matcher.call(node) } ||
         @fallback_rules.find { |r| r.matcher.call(node) }
  rule&.emitter
end

#register(matcher, &emitter) ⇒ Object



51
52
53
54
# File 'lib/ruby_ui_converter/component_map.rb', line 51

def register(matcher, &emitter)
  @rules << Rule.new(matcher, emitter)
  self
end

#register_fallback(matcher, &emitter) ⇒ Object



56
57
58
59
# File 'lib/ruby_ui_converter/component_map.rb', line 56

def register_fallback(matcher, &emitter)
  @fallback_rules << Rule.new(matcher, emitter)
  self
end