Class: LocoMotion::BaseComponent
- Inherits:
-
ViewComponent::Base
- Object
- ViewComponent::Base
- LocoMotion::BaseComponent
- Includes:
- RailsHeroicon::Helper
- Defined in:
- lib/loco_motion/base_component.rb
Direct Known Subclasses
Daisy::Actions::ButtonComponent, Daisy::Actions::DropdownComponent, Daisy::Actions::ModalComponent, Daisy::Actions::SwapComponent, Daisy::Actions::ThemeControllerComponent, Daisy::Actions::ThemePreviewComponent, Daisy::DataDisplay::AccordionComponent, Daisy::DataDisplay::AvatarComponent, Daisy::DataDisplay::BadgeComponent, Daisy::DataDisplay::CardComponent, Daisy::DataDisplay::CarouselComponent, Daisy::DataDisplay::ChatComponent, Daisy::DataDisplay::CollapseComponent, Daisy::DataDisplay::CountdownComponent, Daisy::DataDisplay::DiffComponent, Daisy::DataDisplay::FigureComponent, Daisy::DataDisplay::KbdComponent, Daisy::DataDisplay::ListComponent, Daisy::DataDisplay::ListItemComponent, Daisy::DataDisplay::StatComponent, Daisy::DataDisplay::StatusComponent, Daisy::DataDisplay::TableComponent, Daisy::DataDisplay::TimelineComponent, Daisy::DataDisplay::TimelineEventComponent, Daisy::DataInput::CallyComponent, Daisy::DataInput::CallyComponent::MonthComponent, Daisy::DataInput::CallyInputComponent, Daisy::DataInput::CheckboxComponent, Daisy::DataInput::FieldsetComponent, Daisy::DataInput::FileInputComponent, Daisy::DataInput::FilterComponent, Daisy::DataInput::LabelComponent, Daisy::DataInput::RadioButtonComponent, Daisy::DataInput::RangeComponent, Daisy::DataInput::RatingComponent, Daisy::DataInput::SelectComponent, Daisy::DataInput::TextAreaComponent, Daisy::DataInput::TextInputComponent, Daisy::Feedback::AlertComponent, Daisy::Feedback::LoadingComponent, Daisy::Feedback::ProgressComponent, Daisy::Feedback::RadialProgressComponent, Daisy::Feedback::SkeletonComponent, Daisy::Feedback::ToastComponent, Daisy::Feedback::TooltipComponent, Daisy::Layout::DividerComponent, Daisy::Layout::DrawerComponent, Daisy::Layout::DrawerSidebarComponent, Daisy::Layout::FooterComponent, Daisy::Layout::HeroComponent, Daisy::Layout::IndicatorComponent, Daisy::Layout::JoinComponent, Daisy::Layout::StackComponent, Daisy::Mockup::BrowserComponent, Daisy::Mockup::CodeComponent, Daisy::Mockup::CodeLineComponent, Daisy::Mockup::DeviceComponent, Daisy::Mockup::FrameComponent, Daisy::Navigation::BreadcrumbItemComponent, Daisy::Navigation::BreadcrumbsComponent, Daisy::Navigation::DockComponent, Daisy::Navigation::DockSectionComponent, Daisy::Navigation::LinkComponent, Daisy::Navigation::MenuComponent, Daisy::Navigation::MenuItemComponent, Daisy::Navigation::NavbarComponent, Daisy::Navigation::StepComponent, Daisy::Navigation::StepsComponent, Daisy::Navigation::TabsComponent, Hero::IconComponent, BasicComponent
Constant Summary collapse
- SELF_CLOSING_TAGS =
i[area base br col hr img input keygen link param source track wbr].freeze
- EMPTY_PART_IGNORED_TAGS =
i[textarea].freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Return the current configuration of this component.
-
#loco_parent ⇒ Object
readonly
Returns the value of attribute loco_parent.
Class Method Summary collapse
-
.build(*build_args, **build_kws, &build_block) ⇒ Object
Allows you to bulid a customized version of this component without having to define a new class.
-
.define_modifier(modifier_name) ⇒ Object
Defines a single modifier of this component.
-
.define_modifiers(*modifier_names) ⇒ Object
Define multiple modifiers for this component.
-
.define_part(part_name, part_defaults = {}) ⇒ Object
Defines a new part of this component which can customize CSS, HTML and more.
-
.define_parts(*part_names) ⇒ Object
Convenience method for defining multiple parts at once with no defaults.
-
.define_size(size_name) ⇒ Object
Define a single size of this component.
-
.define_sizes(*size_names) ⇒ Object
Define multiple sizes for this component.
-
.register_component_initializer(method_name) ⇒ Object
Register an instance method to be called during component initialization.
-
.register_component_setup(method_name) ⇒ Object
Register an instance method to be called before component rendering.
-
.renders_many(*args) ⇒ Object
Override the default many slot to render the BasicComponent if no component is provided.
-
.renders_one(*args) ⇒ Object
Override the default slot to render the BasicComponent if no component is provided.
-
.set_component_name(component_name) ⇒ Object
Sets the component name used in CSS generation.
Instance Method Summary collapse
-
#before_render ⇒ Object
Run registered setup hooks from concerns before rendering.
-
#component_ref ⇒ BaseComponent
Returns a reference to this component.
-
#config_option(key, default = nil) ⇒ Object
Retrieve the requested component option, or the desired default if no option was provided.
-
#cssify(content) ⇒ Object
Convert strings, symbols, and arrays of those into a single CSS-like string.
- #empty_part_content(tag_name) ⇒ Object
-
#initialize(*args, **kws, &block) ⇒ BaseComponent
constructor
Create a new instance of a component.
-
#inspect ⇒ Object
Provide some nice output for debugging or other purposes.
-
#part(part_name, &block) ⇒ Object
Renders the given part.
-
#rendered_css(part_name) ⇒ String
Builds a string suitable for the HTML element’s ‘class` attribute for the requested component part.
-
#rendered_data(part_name) ⇒ Hash
Builds the HTML ‘data` attribute.
-
#rendered_html(part_name) ⇒ Hash
Builds a Hash of all of the HTML attributes for the requested component part.
-
#rendered_stimulus_controllers(part_name) ⇒ Object
Builds a list of Stimulus controllers for the HTML ‘data-controller` attribute.
-
#rendered_tag_name(part_name) ⇒ Symbol, String
Returns the user-provided or component-default HTML tag-name.
-
#set_loco_parent(parent) ⇒ Object
Sets the parent component of this component.
-
#strip_spaces(str) ⇒ String
Strip extra whitespace from a given string.
Constructor Details
#initialize(*args, **kws, &block) ⇒ BaseComponent
Create a new instance of a component.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/loco_motion/base_component.rb', line 33 def initialize(*args, **kws, &block) super # Create our config object @config = LocoMotion::ComponentConfig.new(self, **kws, &block) # Run registered initializer hooks from concerns self.class.component_initializers.each { |initializer| send(initializer) } # Allow certain components to skip styling if they are being inherited @skip_styling = config_option(:skip_styling, false) # Allow manual passing of the loco parent on init if it's not auto-set # via slots @loco_parent = kws[:loco_parent] if kws.key?(:loco_parent) end |
Instance Attribute Details
#config ⇒ Object (readonly)
Return the current configuration of this component.
22 23 24 |
# File 'lib/loco_motion/base_component.rb', line 22 def config @config end |
#loco_parent ⇒ Object (readonly)
Returns the value of attribute loco_parent.
244 245 246 |
# File 'lib/loco_motion/base_component.rb', line 244 def loco_parent @loco_parent end |
Class Method Details
.build(*build_args, **build_kws, &build_block) ⇒ Object
Allows you to bulid a customized version of this component without having to define a new class.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/loco_motion/base_component.rb', line 191 def self.build(*build_args, **build_kws, &build_block) klass = Class.new(self) # Unless already defined, delegate the name method to the superclass so # ViewComponent can find the sidecar partials and render them when no call # method is defined. unless klass.method_defined?(:name) klass.instance_eval do def name superclass.name end end end # Override the initialize method to combine the build and instance args klass.class_eval do original_initialize = method_defined?(:initialize) ? instance_method(:initialize) : nil define_method(:initialize) do |*instance_args, **instance_kws, &instance_block| if original_initialize original_initialize.bind(self).call else super(*instance_args, **instance_kws, &instance_block) end @config.smart_merge!(**build_kws) end end # Finally, execute any block they passed in to allow for customizations klass.class_eval(&build_block) if block_given? klass end |
.define_modifier(modifier_name) ⇒ Object
Defines a single modifier of this component. Modifiers control certain rendering aspects of the component.
138 139 140 |
# File 'lib/loco_motion/base_component.rb', line 138 def self.define_modifier(modifier_name) define_modifiers(modifier_name) end |
.define_modifiers(*modifier_names) ⇒ Object
Define multiple modifiers for this component. Modifiers control certain rendering aspects of the component.
149 150 151 152 153 154 155 156 157 158 |
# File 'lib/loco_motion/base_component.rb', line 149 def self.define_modifiers(*modifier_names) # Note that since we're using Rails' class_attribute method for these, we # must take care not to alter the original object but rather use a setter # (the `+=` in this case) to set the new value so Rails knows not to # override the parent value. # # For example, we cannot use `<<` or `concat` here. self.valid_modifiers ||= [] self.valid_modifiers += modifier_names end |
.define_part(part_name, part_defaults = {}) ⇒ Object
Defines a new part of this component which can customize CSS, HTML and more.
91 92 93 94 95 96 97 98 99 |
# File 'lib/loco_motion/base_component.rb', line 91 def self.define_part(part_name, part_defaults = {}) # Note that since we're using Rails' class_attribute method for these, we # must take care not to alter the original object but rather use a setter # (the `=` in this case) to set the new value so Rails knows not to override # the parent value. # # For example, we cannot use `merge!` or `[part_name] = ` here. self.component_parts = component_parts.merge({ part_name => part_defaults }) end |
.define_parts(*part_names) ⇒ Object
Convenience method for defining multiple parts at once with no defaults.
106 107 108 109 110 |
# File 'lib/loco_motion/base_component.rb', line 106 def self.define_parts(*part_names) (part_names || []).each do |part_name| define_part(part_name) end end |
.define_size(size_name) ⇒ Object
Define a single size of this component. Sizes control how big or small this component will render.
166 167 168 |
# File 'lib/loco_motion/base_component.rb', line 166 def self.define_size(size_name) define_sizes(size_name) end |
.define_sizes(*size_names) ⇒ Object
Define multiple sizes for this component. Sizes control how big or small this component will render.
176 177 178 179 180 181 182 183 184 185 |
# File 'lib/loco_motion/base_component.rb', line 176 def self.define_sizes(*size_names) # Note that since we're using Rails' class_attribute method for these, we # must take care not to alter the original object but rather use a setter # (the `+=` in this case) to set the new value so Rails knows not to # override the parent value. # # For example, we cannot use `<<` or `concat` here. self.valid_sizes ||= [] self.valid_sizes += size_names end |
.register_component_initializer(method_name) ⇒ Object
Register an instance method to be called during component initialization.
117 118 119 120 |
# File 'lib/loco_motion/base_component.rb', line 117 def self.register_component_initializer(method_name) # Ensure we don't modify the parent class's array directly self.component_initializers += [method_name.to_sym] end |
.register_component_setup(method_name) ⇒ Object
Register an instance method to be called before component rendering.
127 128 129 130 |
# File 'lib/loco_motion/base_component.rb', line 127 def self.register_component_setup(method_name) # Ensure we don't modify the parent class's array directly self.component_setups += [method_name.to_sym] end |
.renders_many(*args) ⇒ Object
Override the default many slot to render the BasicComponent if no component is provided.
71 72 73 74 |
# File 'lib/loco_motion/base_component.rb', line 71 def self.renders_many(*args) # If they don't pass extra options, default to BasicComponent args&.size == 1 ? super(*args + [LocoMotion::BasicComponent]) : super end |
.renders_one(*args) ⇒ Object
Override the default slot to render the BasicComponent if no component is provided.
62 63 64 65 |
# File 'lib/loco_motion/base_component.rb', line 62 def self.renders_one(*args) # If they don't pass extra options, default to BasicComponent args&.size == 1 ? super(*args + [LocoMotion::BasicComponent]) : super end |
.set_component_name(component_name) ⇒ Object
Sets the component name used in CSS generation.
81 82 83 |
# File 'lib/loco_motion/base_component.rb', line 81 def self.set_component_name(component_name) self.component_name = component_name end |
Instance Method Details
#before_render ⇒ Object
Run registered setup hooks from concerns before rendering.
53 54 55 56 |
# File 'lib/loco_motion/base_component.rb', line 53 def before_render # Note: ViewComponent::Base does not define before_render, so no super call needed. self.class.component_setups.each { |setup| send(setup) } end |
#component_ref ⇒ BaseComponent
Returns a reference to this component. Useful for passing a parent component into child components.
232 233 234 |
# File 'lib/loco_motion/base_component.rb', line 232 def component_ref self end |
#config_option(key, default = nil) ⇒ Object
Retrieve the requested component option, or the desired default if no option was provided.
393 394 395 396 397 |
# File 'lib/loco_motion/base_component.rb', line 393 def config_option(key, default = nil) value = @config.[key] value.nil? ? default : value end |
#cssify(content) ⇒ Object
Convert strings, symbols, and arrays of those into a single CSS-like string.
367 368 369 370 371 |
# File 'lib/loco_motion/base_component.rb', line 367 def cssify(content) css = [content].flatten.compact strip_spaces(css.join(" ")) end |
#empty_part_content(tag_name) ⇒ Object
277 278 279 280 281 |
# File 'lib/loco_motion/base_component.rb', line 277 def empty_part_content(tag_name) unless EMPTY_PART_IGNORED_TAGS.include?(tag_name.to_sym) "<!-- Empty Part Block //-->".html_safe end end |
#inspect ⇒ Object
Provide some nice output for debugging or other purposes.
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 |
# File 'lib/loco_motion/base_component.rb', line 401 def inspect parts = component_parts.map do |part_name, part_defaults| { part_name: part_name, tag_name: rendered_tag_name(part_name), css: rendered_css(part_name), html: rendered_html(part_name) } end [ "#<#{self.class.name}", "@component_name=#{(component_name || :unnamed).inspect}", "@valid_modifiers=#{valid_modifiers.inspect}", "@valid_sizes=#{valid_sizes.inspect}", "@config=#{@config.inspect}", "@component_parts=#{parts.inspect}", "@loco_parent=#{loco_parent.inspect}", ].join(" ") + ">" end |
#part(part_name, &block) ⇒ Object
Renders the given part.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/loco_motion/base_component.rb', line 249 def part(part_name, &block) # Validate the part_name @config.validate_part(part_name) # Grab the rendered tag name tag_name = rendered_tag_name(part_name) if block_given? content_tag(tag_name, **rendered_html(part_name), &block) else # The `tag()` helper will allow you to pass any tag without a block, but # this isn't valid HTML. In particular, it will render a "self-closing" # <div /> tag which doesn't actually close the div. # # Therefore, we need to pass some kind of block to ensure it closes. We've # choosen a comment to keep the output as clean as possible while still # informing a developer what is happening. if SELF_CLOSING_TAGS.include?(tag_name.to_sym) tag(tag_name, **rendered_html(part_name)) else content_tag(tag_name, **rendered_html(part_name)) do empty_part_content(tag_name) end end end end |
#rendered_css(part_name) ⇒ String
Builds a string suitable for the HTML element’s ‘class` attribute for the requested component part.
304 305 306 307 308 309 |
# File 'lib/loco_motion/base_component.rb', line 304 def rendered_css(part_name) default_css = @config.get_part(part_name)[:default_css] user_css = @config.get_part(part_name)[:user_css] cssify([default_css, user_css]) end |
#rendered_data(part_name) ⇒ Hash
Builds the HTML ‘data` attribute.
338 339 340 341 342 343 344 345 346 |
# File 'lib/loco_motion/base_component.rb', line 338 def rendered_data(part_name) generated_data = {} stimulus_controllers = rendered_stimulus_controllers(part_name) generated_data[:controller] = stimulus_controllers if stimulus_controllers.present? generated_data end |
#rendered_html(part_name) ⇒ Hash
Builds a Hash of all of the HTML attributes for the requested component part.
320 321 322 323 324 325 326 327 328 |
# File 'lib/loco_motion/base_component.rb', line 320 def rendered_html(part_name) default_html = @config.get_part(part_name)[:default_html] || {} user_html = @config.get_part(part_name)[:user_html] || {} generated_html = { class: rendered_css(part_name), data: rendered_data(part_name) }.deep_merge(default_html).deep_merge(user_html) end |
#rendered_stimulus_controllers(part_name) ⇒ Object
Builds a list of Stimulus controllers for the HTML ‘data-controller` attribute.
@ return [String] A space-separated list of Stimulus controllers.
357 358 359 360 361 362 |
# File 'lib/loco_motion/base_component.rb', line 357 def rendered_stimulus_controllers(part_name) default_controllers = @config.get_part(part_name)[:default_stimulus_controllers] user_controllers = @config.get_part(part_name)[:user_stimulus_controllers] strip_spaces([default_controllers, user_controllers].join(" ")) end |
#rendered_tag_name(part_name) ⇒ Symbol, String
Returns the user-provided or component-default HTML tag-name.
290 291 292 293 294 |
# File 'lib/loco_motion/base_component.rb', line 290 def rendered_tag_name(part_name) part = @config.get_part(part_name) part[:user_tag_name] || part[:default_tag_name] end |
#set_loco_parent(parent) ⇒ Object
Sets the parent component of this component. Enables child components to ask questions of their parent and access parent config.
241 242 243 |
# File 'lib/loco_motion/base_component.rb', line 241 def set_loco_parent(parent) @loco_parent = parent end |
#strip_spaces(str) ⇒ String
Strip extra whitespace from a given string.
380 381 382 |
# File 'lib/loco_motion/base_component.rb', line 380 def strip_spaces(str) str.gsub(/ +/, " ").strip end |