Class: HakumiComponents::BaseComponent

Inherits:
ViewComponent::Base
  • Object
show all
Extended by:
T::Sig
Defined in:
app/components/hakumi_components/base_component.rb

Direct Known Subclasses

AdminPanel::Component, Affix::Component, Alert::Component, Anchor::Component, Anchor::Link::Component, Autocomplete::Component, Avatar::Component, HakumiComponents::Badge::Component, HakumiComponents::Breadcrumb::Component, HakumiComponents::Breadcrumb::Item::Component, HakumiComponents::Button::Component, Calendar::Component, Card::Component, Card::Grid::Component, Card::Meta::Component, Carousel::Component, Cascader::Component, Checkbox::Component, Checkbox::Group::Component, Collapse::Component, Collapse::Panel::Component, ColorPicker::Component, Container::Component, DatePicker::Component, DatePicker::RangePicker, Descriptions::Component, Descriptions::Item::Component, Divider::Component, Drawer::Component, Dropdown::Component, Dropdown::Divider::Component, Dropdown::Item::Component, Empty::Component, Flex::Component, FloatButton::BackTop::Component, FloatButton::Component, FloatButton::Group::Component, FloatButton::GroupCluster::Component, Form::Item::Component, Grid::Col::Component, Grid::Row::Component, Icon::Component, Image::Component, Image::PreviewGroup::Component, Input::Component, Input::TextArea::Component, InputNumber::Component, Layout::Component, Layout::Content::Component, Layout::Footer::Component, Layout::Header::Component, Layout::Sider::Component, Mentions::Component, Menu::Component, Menu::Divider::Component, Menu::Group::Component, Menu::Item::Component, Menu::SubMenu::Component, Message::Component, Modal::Component, Modal::Confirm::Component, Modal::StatusComponent, Notification::Component, Pagination::Component, Popconfirm::Component, Popover::Component, Progress::Component, QrCode::Component, Radio::Component, Radio::Group::Component, Rate::Component, Result::Component, Segmented::Component, Select::Component, Skeleton::Avatar::Component, Skeleton::Button::Component, Skeleton::Component, Skeleton::Image::Component, Skeleton::Input::Component, Skeleton::Node::Component, Slider::Component, Space::Compact::Component, Space::Component, Spin::Component, Splitter::Component, Splitter::Panel::Component, Statistic::Component, Steps::Component, Steps::Item::Component, Switch::Component, Table::Column::Component, Table::ColumnGroup::Component, Table::Component, Tabs::Component, Tabs::Item::Component, Tag::Component, Tag::Group::Component, TimePicker::Component, Timeline::Component, Timeline::Item::Component, Tooltip::Component, Tour::Component, Transfer::Component, Tree::Component, TreeSelect::Component, Typography::BaseComponent, Upload::Component

Constant Summary collapse

SizeValue =
T.type_alias { T.any(Types::HtmlKey, Integer) }
DimensionInput =
T.type_alias { T.nilable(T.any(Numeric, String)) }
I18nOptionValue =
T.type_alias { Types::ValidationComparable }
DateLikeValue =
T.type_alias { T.any(Date, Time, DateTime, ActiveSupport::TimeWithZone) }
DateInput =
T.type_alias { T.nilable(T.any(DateLikeValue, Types::HtmlKey)) }
SymbolInput =
T.type_alias { T.nilable(Types::HtmlKey) }
PresenceArray =
T.type_alias { T::Array[HakumiComponents::Types::FormFieldScalar] }
ControllerOptions =
T.type_alias { T.any(ActionController::Parameters, Types::HtmlAttributes) }
PresenceScalar =
T.type_alias { T.any(Types::ValidationPrimitive, DateLikeValue) }
HtmlPayloadInput =
T.type_alias { T.any(ActionController::Parameters, Types::DataAttributes, Types::HtmlClassList) }
PresenceValue =
T.type_alias do
  T.nilable(T.any(
    Types::Renderable,
    PresenceScalar,
    PresenceArray
  ))
end
RawHtmlInput =
T.type_alias do
  T.nilable(T.any(
    Types::HtmlPrimitive,
    HtmlPayloadInput
  ))
end
SIZES =
T.let(%i[small default large].freeze, T::Array[Symbol])

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.boolean_html_param(value) ⇒ Object



210
211
212
213
214
215
# File 'app/components/hakumi_components/base_component.rb', line 210

def self.boolean_html_param(value)
  scalar = html_primitive_param(value)
  return nil if scalar.nil?

  cast_boolean(scalar)
end

.cast_boolean(value) ⇒ Object



184
185
186
# File 'app/components/hakumi_components/base_component.rb', line 184

def self.cast_boolean(value)
  BOOLEAN_CASTER.cast(value)
end

.float_html_param(value) ⇒ Object



233
234
235
236
237
238
# File 'app/components/hakumi_components/base_component.rb', line 233

def self.float_html_param(value)
  return value.to_f if value.is_a?(String)
  return value.to_f if value.is_a?(Numeric)

  nil
end

.html_param(params, key) ⇒ Object



205
206
207
# File 'app/components/hakumi_components/base_component.rb', line 205

def self.html_param(params, key)
  coerce_html_attribute_value(params[key])
end

.html_primitive_param(value) ⇒ Object



189
190
191
192
193
194
195
196
197
# File 'app/components/hakumi_components/base_component.rb', line 189

def self.html_primitive_param(value)
  return value if value.is_a?(String)
  return value if value.is_a?(Symbol)
  return value if value.is_a?(TrueClass)
  return value if value.is_a?(FalseClass)
  return value if value.is_a?(Numeric)

  nil
end

.integer_html_param(value) ⇒ Object



225
226
227
228
229
230
# File 'app/components/hakumi_components/base_component.rb', line 225

def self.integer_html_param(value)
  return value if value.is_a?(Integer)
  return value.to_i if value.is_a?(String)

  nil
end

.string_html_param(value) ⇒ Object



241
242
243
244
245
246
247
# File 'app/components/hakumi_components/base_component.rb', line 241

def self.string_html_param(value)
  return nil if value.nil?
  return value if value.is_a?(String)
  return value.to_s if value.is_a?(Symbol) || value.is_a?(Numeric)

  nil
end

.string_or_symbol_array_html_param(value) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
# File 'app/components/hakumi_components/base_component.rb', line 250

def self.string_or_symbol_array_html_param(value)
  return nil unless value.is_a?(Array)

  entries = T.let([], Types::StringOrSymbolArray)
  value.each do |entry|
    next if entry.nil?

    entries << entry
  end
  entries
end

.symbol_html_param(value) ⇒ Object



218
219
220
221
222
# File 'app/components/hakumi_components/base_component.rb', line 218

def self.symbol_html_param(value)
  return value.to_sym if value.is_a?(String) || value.is_a?(Symbol)

  nil
end

Instance Method Details

#append_data_token(existing, token) ⇒ Object



174
175
176
177
178
# File 'app/components/hakumi_components/base_component.rb', line 174

def append_data_token(existing, token)
  return token unless existing.is_a?(String) && existing.present?

  [ existing, token ].join(" ")
end

#build_inline_style(styles) ⇒ Object



316
317
318
319
320
321
322
323
324
325
326
327
# File 'app/components/hakumi_components/base_component.rb', line 316

def build_inline_style(styles)
  return nil if styles.respond_to?(:blank?) && styles.blank?

  style_array = if styles.is_a?(Hash)
    styles.compact.map { |key, value| "#{key}: #{value}" }
  else
    Array(styles).compact
  end

  return nil if style_array.empty?
  style_array.join("; ")
end

#cast_boolean(value) ⇒ Object



311
312
313
# File 'app/components/hakumi_components/base_component.rb', line 311

def cast_boolean(value)
  self.class.cast_boolean(value)
end

#class_names(base, modifiers = {}, extras = []) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/components/hakumi_components/base_component.rb', line 35

def class_names(base, modifiers = {}, extras = [])
  classes = [ "hakumi-#{base}" ]

  modifiers.each do |key, value|
    next if value.nil? || value == false

    if value == true
      classes << "hakumi-#{base}-#{key}"
    else
      classes << "hakumi-#{base}-#{key}-#{value}"
    end
  end

  classes.concat(Array(extras).compact)
  classes.join(" ")
end

#component_classes(base, modifiers = {}, attributes = {}) ⇒ Object



338
339
340
# File 'app/components/hakumi_components/base_component.rb', line 338

def component_classes(base, modifiers = {}, attributes = {})
  class_names(base, modifiers, html_classes(attributes))
end

#data_attributes_from(value) ⇒ Object



163
164
165
166
167
168
169
170
171
# File 'app/components/hakumi_components/base_component.rb', line 163

def data_attributes_from(value)
  attrs = T.let({}, Types::DataAttributes)
  return attrs unless value.is_a?(Hash)

  value.each do |key, entry|
    attrs[key] = entry
  end
  attrs
end

#dimension_to_css(value) ⇒ Object



141
142
143
144
145
146
147
148
# File 'app/components/hakumi_components/base_component.rb', line 141

def dimension_to_css(value)
  return nil if value.nil?
  return value if value.is_a?(String) && value.strip.present?
  return "#{value}px" if value.is_a?(Numeric)

  string = value.to_s.strip
  string.empty? ? nil : string
end

#ensure_dom_id!(attrs) ⇒ Object



108
109
110
111
112
113
114
115
116
117
# File 'app/components/hakumi_components/base_component.rb', line 108

def ensure_dom_id!(attrs)
  data = attrs[:data]
  controllers = data.is_a?(Hash) ? data[:controller] : nil
  return unless controllers.is_a?(String) && !controllers.empty?

  id = attrs[:id]
  return if id.is_a?(String) && !id.empty?

  attrs[:id] = generate_id("hakumi-component")
end

#generate_id(prefix, length: 4) ⇒ Object



120
121
122
# File 'app/components/hakumi_components/base_component.rb', line 120

def generate_id(prefix, length: 4)
  "#{prefix}_#{SecureRandom.hex(length)}"
end

#html_classes(attributes) ⇒ Object



330
331
332
333
334
335
# File 'app/components/hakumi_components/base_component.rb', line 330

def html_classes(attributes)
  value = attributes[:class]
  return value if value.is_a?(String) || value.is_a?(Array)

  nil
end

#html_style(attributes) ⇒ Object



343
344
345
346
# File 'app/components/hakumi_components/base_component.rb', line 343

def html_style(attributes)
  value = attributes[:style]
  value.is_a?(String) ? value : nil
end

#i18n_scopeObject



359
360
361
362
363
364
365
366
367
368
369
370
# File 'app/components/hakumi_components/base_component.rb', line 359

def i18n_scope
  @i18n_scope = T.let(@i18n_scope, T.nilable(String)) unless instance_variable_defined?(:@i18n_scope)
  @i18n_scope ||= begin
    class_name = self.class.name
    raise "#{self.class} must have a name for i18n scope" if class_name.nil?

    class_name
    .delete_suffix("::Component")
    .underscore
    .tr("/", ".")
  end
end

#merge_attributes(defaults = {}, overrides = {}) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'app/components/hakumi_components/base_component.rb', line 53

def merge_attributes(defaults = {}, overrides = {})
  merged = T.let(defaults.deep_dup, Types::HtmlAttributes)

  if defaults[:class] && overrides[:class]
    merged[:class] = [ defaults[:class], overrides[:class] ].join(" ")
  elsif overrides[:class]
    merged[:class] = overrides[:class]
  end

  if defaults[:style] && overrides[:style]
    merged[:style] = [ defaults[:style].to_s.chomp(";"), overrides[:style] ].join("; ")
  elsif overrides[:style]
    merged[:style] = overrides[:style]
  end

  merged_data = merge_attribute_hash(defaults[:data], overrides[:data])
  merged[:data] = merged_data if merged_data

  merged_aria = merge_attribute_hash(defaults[:aria], overrides[:aria])
  merged[:aria] = merged_aria if merged_aria

  merged.merge!(overrides.except(:aria, :class, :data, :style))

  ensure_dom_id!(merged)

  merged
end

#render_value(value) ⇒ Object



125
126
127
128
129
130
131
132
# File 'app/components/hakumi_components/base_component.rb', line 125

def render_value(value)
  return nil unless value.present?
  return value if value.is_a?(String) || value.is_a?(ActiveSupport::SafeBuffer)
  return value.to_s if value.is_a?(ViewComponent::Slot)
  return render(value) if value.respond_to?(:render_in)

  nil
end

#size_to_pixels(size, mapping = {}) ⇒ Object



135
136
137
138
# File 'app/components/hakumi_components/base_component.rb', line 135

def size_to_pixels(size, mapping = {})
  return size if size.is_a?(Integer)
  mapping[size.to_sym] || size
end

#stimulus_attrs(controller, values = {}) ⇒ Object



151
152
153
154
155
156
157
158
159
160
# File 'app/components/hakumi_components/base_component.rb', line 151

def stimulus_attrs(controller, values = {})
  data = T.let({ "controller" => controller }, Types::StimulusData)
  values.each do |key, value|
    next if value.nil?

    serialized = value.is_a?(Array) || value.is_a?(Hash) ? value.to_json : value
    data["#{controller}-#{key.to_s.underscore.dasherize}-value"] = serialized
  end
  data
end

#t_default(key, default:, **options) ⇒ Object



354
355
356
# File 'app/components/hakumi_components/base_component.rb', line 354

def t_default(key, default:, **options)
  I18n.t(key, scope: i18n_scope, default: default, **options)
end

#translate_with_default(key, default:, **options) ⇒ Object



349
350
351
# File 'app/components/hakumi_components/base_component.rb', line 349

def translate_with_default(key, default:, **options)
  I18n.t(key, scope: i18n_scope, default: default, **options)
end

#validate_inclusion!(prop, allowed) ⇒ Object

Raises:

  • (ArgumentError)


99
100
101
102
103
104
105
# File 'app/components/hakumi_components/base_component.rb', line 99

def validate_inclusion!(prop, allowed)
  value = instance_variable_get("@#{prop}")
  return if value.nil?
  return if allowed.include?(value)

  raise ArgumentError, "#{prop} must be one of #{allowed.inspect}, got #{value.inspect}"
end

#validate_required!(*props) ⇒ Object



91
92
93
94
95
96
# File 'app/components/hakumi_components/base_component.rb', line 91

def validate_required!(*props)
  props.each do |prop|
    value = instance_variable_get("@#{prop}")
    raise ArgumentError, "#{prop} is required" unless value_present?(value)
  end
end

#value_present?(value) ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
85
86
87
88
# File 'app/components/hakumi_components/base_component.rb', line 82

def value_present?(value)
  return false if value.nil? || value == false
  return !value.empty? if value.is_a?(String)
  return !value.empty? if value.is_a?(Array)

  true
end