fluxbit_view_components 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -0
  3. data/app/assets/javascripts/fluxbit_view_components/assigner_controller.js +49 -0
  4. data/app/assets/javascripts/fluxbit_view_components/auto_submit_controller.js +39 -0
  5. data/app/assets/javascripts/fluxbit_view_components/drawer_controller.js +135 -0
  6. data/app/assets/javascripts/fluxbit_view_components/index.js +56 -0
  7. data/app/assets/javascripts/fluxbit_view_components/method_link_controller.js +143 -0
  8. data/app/assets/javascripts/fluxbit_view_components/modal_controller.js +118 -0
  9. data/app/assets/javascripts/fluxbit_view_components/password_controller.js +170 -0
  10. data/app/assets/javascripts/fluxbit_view_components/progress_controller.js +374 -0
  11. data/app/assets/javascripts/fluxbit_view_components/row_click_controller.js +32 -0
  12. data/app/assets/javascripts/fluxbit_view_components/select_all_controller.js +122 -0
  13. data/app/assets/javascripts/fluxbit_view_components/spinner_percent_controller.js +174 -0
  14. data/app/assets/javascripts/fluxbit_view_components/theme_button_controller.js +90 -0
  15. data/app/assets/javascripts/fluxbit_view_components.js +1175 -0
  16. data/app/components/fluxbit/accordion_component.rb +125 -0
  17. data/app/components/fluxbit/alert_component.rb +8 -8
  18. data/app/components/fluxbit/avatar_component.rb +11 -12
  19. data/app/components/fluxbit/avatar_group_component.rb +1 -1
  20. data/app/components/fluxbit/badge_component.rb +8 -7
  21. data/app/components/fluxbit/banner_component.rb +139 -0
  22. data/app/components/fluxbit/bottom_navigation_component.rb +437 -0
  23. data/app/components/fluxbit/breadcrumb_component.rb +66 -0
  24. data/app/components/fluxbit/button_component.rb +39 -11
  25. data/app/components/fluxbit/button_group_component.rb +1 -1
  26. data/app/components/fluxbit/card_component.rb +26 -23
  27. data/app/components/fluxbit/carousel_component.rb +154 -0
  28. data/app/components/fluxbit/component.rb +24 -3
  29. data/app/components/fluxbit/drawer_component.html.erb +30 -0
  30. data/app/components/fluxbit/drawer_component.rb +125 -0
  31. data/app/components/fluxbit/dropdown_component.rb +41 -0
  32. data/app/components/fluxbit/dropdown_item_component.rb +68 -0
  33. data/app/components/fluxbit/flex_component.rb +1 -1
  34. data/app/components/fluxbit/form/component.rb +15 -8
  35. data/app/components/fluxbit/form/dropzone_component.rb +3 -3
  36. data/app/components/fluxbit/form/field_component.rb +4 -2
  37. data/app/components/fluxbit/form/help_text_component.rb +1 -1
  38. data/app/components/fluxbit/form/label_component.rb +10 -3
  39. data/app/components/fluxbit/form/password_component.rb +247 -0
  40. data/app/components/fluxbit/form/radio_group_button_component.rb +126 -0
  41. data/app/components/fluxbit/form/select_component.rb +108 -11
  42. data/app/components/fluxbit/form/text_field_component.rb +40 -23
  43. data/app/components/fluxbit/form/toggle_component.rb +2 -2
  44. data/app/components/fluxbit/form/upload_image_component.html.erb +3 -3
  45. data/app/components/fluxbit/form/upload_image_component.rb +12 -1
  46. data/app/components/fluxbit/gravatar_component.rb +7 -0
  47. data/app/components/fluxbit/icon_helpers.rb +167 -0
  48. data/app/components/fluxbit/link_component.rb +42 -0
  49. data/app/components/fluxbit/modal_component.rb +28 -31
  50. data/app/components/fluxbit/pagination_component.rb +206 -0
  51. data/app/components/fluxbit/popover_component.rb +14 -14
  52. data/app/components/fluxbit/progress_component.rb +196 -0
  53. data/app/components/fluxbit/skeleton_component.rb +237 -0
  54. data/app/components/fluxbit/speed_dial_action_component.html.erb +30 -0
  55. data/app/components/fluxbit/speed_dial_action_component.rb +59 -0
  56. data/app/components/fluxbit/speed_dial_component.html.erb +33 -0
  57. data/app/components/fluxbit/speed_dial_component.rb +73 -0
  58. data/app/components/fluxbit/spinner_component.rb +71 -0
  59. data/app/components/fluxbit/spinner_percent_component.rb +174 -0
  60. data/app/components/fluxbit/stepper_component.rb +223 -0
  61. data/app/components/fluxbit/tab_component.rb +44 -25
  62. data/app/components/fluxbit/table_component.rb +186 -0
  63. data/app/components/fluxbit/table_group_component.rb +28 -0
  64. data/app/components/fluxbit/theme_button_component.rb +64 -0
  65. data/app/components/fluxbit/timeline_component.rb +63 -0
  66. data/app/components/fluxbit/timeline_item_component.html.erb +64 -0
  67. data/app/components/fluxbit/timeline_item_component.rb +78 -0
  68. data/app/components/fluxbit/tooltip_component.rb +2 -2
  69. data/app/helpers/fluxbit/components_helper.rb +74 -4
  70. data/app/helpers/fluxbit/form_builder.rb +64 -15
  71. data/app/helpers/fluxbit/view_helper.rb +71 -0
  72. data/config/locales/en.yml +37 -4
  73. data/config/locales/pt-BR.yml +36 -0
  74. data/lib/fluxbit/config/accordion_component.rb +73 -0
  75. data/lib/fluxbit/config/avatar_component.rb +11 -11
  76. data/lib/fluxbit/config/badge_component.rb +14 -11
  77. data/lib/fluxbit/config/banner_component.rb +60 -0
  78. data/lib/fluxbit/config/bottom_navigation_component.rb +74 -0
  79. data/lib/fluxbit/config/breadcrumb_component.rb +24 -0
  80. data/lib/fluxbit/config/button_component.rb +6 -4
  81. data/lib/fluxbit/config/card_component.rb +23 -12
  82. data/lib/fluxbit/config/carousel_component.rb +33 -0
  83. data/lib/fluxbit/config/drawer_component.rb +48 -0
  84. data/lib/fluxbit/config/dropdown_component.rb +29 -0
  85. data/lib/fluxbit/config/form/check_box_component.rb +1 -1
  86. data/lib/fluxbit/config/form/dropzone_component.rb +1 -1
  87. data/lib/fluxbit/config/form/help_text_component.rb +1 -1
  88. data/lib/fluxbit/config/form/label_component.rb +3 -2
  89. data/lib/fluxbit/config/form/password_component.rb +19 -0
  90. data/lib/fluxbit/config/form/radio_group_button_component.rb +24 -0
  91. data/lib/fluxbit/config/form/text_field_component.rb +11 -11
  92. data/lib/fluxbit/config/form/toggle_component.rb +5 -5
  93. data/lib/fluxbit/config/link_component.rb +24 -0
  94. data/lib/fluxbit/config/modal_component.rb +1 -1
  95. data/lib/fluxbit/config/pagination_component.rb +31 -0
  96. data/lib/fluxbit/config/popover_component.rb +1 -1
  97. data/lib/fluxbit/config/progress_component.rb +63 -0
  98. data/lib/fluxbit/config/skeleton_component.rb +82 -0
  99. data/lib/fluxbit/config/speed_dial_component.rb +50 -0
  100. data/lib/fluxbit/config/spinner_component.rb +30 -0
  101. data/lib/fluxbit/config/spinner_percent_component.rb +61 -0
  102. data/lib/fluxbit/config/stepper_component.rb +299 -0
  103. data/lib/fluxbit/config/tab_component.rb +6 -0
  104. data/lib/fluxbit/config/table_component.rb +75 -0
  105. data/lib/fluxbit/config/theme_button_component.rb +19 -0
  106. data/lib/fluxbit/config/timeline_component.rb +77 -0
  107. data/lib/fluxbit/view_components/engine.rb +11 -3
  108. data/lib/fluxbit/view_components/version.rb +1 -1
  109. data/lib/fluxbit/view_components.rb +20 -0
  110. data/lib/generators/fluxbit/devise_views_generator.rb +116 -0
  111. data/lib/generators/fluxbit/pagy_generator.rb +39 -0
  112. data/lib/generators/fluxbit/scaffold_generator.rb +165 -0
  113. data/lib/generators/fluxbit/templates/_alert.html.erb.tt +1 -0
  114. data/lib/generators/fluxbit/templates/_flash.html.erb.tt +15 -0
  115. data/lib/generators/fluxbit/templates/_form.html.erb.tt +38 -0
  116. data/lib/generators/fluxbit/templates/_metadata.html.erb.tt +44 -0
  117. data/lib/generators/fluxbit/templates/controller.rb.tt +406 -0
  118. data/lib/generators/fluxbit/templates/create.turbo_stream.erb.tt +7 -0
  119. data/lib/generators/fluxbit/templates/destroy.turbo_stream.erb.tt +3 -0
  120. data/lib/generators/fluxbit/templates/destroy_all.turbo_stream.erb.tt +9 -0
  121. data/lib/generators/fluxbit/templates/devise_views/confirmations/new.html.erb +11 -0
  122. data/lib/generators/fluxbit/templates/devise_views/layouts/devise.html.erb +64 -0
  123. data/lib/generators/fluxbit/templates/devise_views/mailer/confirmation_instructions.html.erb +5 -0
  124. data/lib/generators/fluxbit/templates/devise_views/mailer/email_changed.html.erb +7 -0
  125. data/lib/generators/fluxbit/templates/devise_views/mailer/password_changed.html.erb +3 -0
  126. data/lib/generators/fluxbit/templates/devise_views/mailer/reset_password_instructions.html.erb +8 -0
  127. data/lib/generators/fluxbit/templates/devise_views/mailer/unlock_instructions.html.erb +7 -0
  128. data/lib/generators/fluxbit/templates/devise_views/passwords/edit.html.erb +29 -0
  129. data/lib/generators/fluxbit/templates/devise_views/passwords/new.html.erb +11 -0
  130. data/lib/generators/fluxbit/templates/devise_views/registrations/edit.html.erb +43 -0
  131. data/lib/generators/fluxbit/templates/devise_views/registrations/new.html.erb +34 -0
  132. data/lib/generators/fluxbit/templates/devise_views/sessions/new.html.erb +15 -0
  133. data/lib/generators/fluxbit/templates/devise_views/shared/_error_messages.html.erb +14 -0
  134. data/lib/generators/fluxbit/templates/devise_views/shared/_links.html.erb +25 -0
  135. data/lib/generators/fluxbit/templates/devise_views/unlocks/new.html.erb +11 -0
  136. data/lib/generators/fluxbit/templates/edit.html.erb.tt +47 -0
  137. data/lib/generators/fluxbit/templates/fluxbit_pagy.css +27 -0
  138. data/lib/generators/fluxbit/templates/i18n.en.yml.tt +121 -0
  139. data/lib/generators/fluxbit/templates/i18n.pt-BR.yml.tt +121 -0
  140. data/lib/generators/fluxbit/templates/index.html.erb.tt +254 -0
  141. data/lib/generators/fluxbit/templates/index.json.jbuilder.tt +33 -0
  142. data/lib/generators/fluxbit/templates/new.html.erb.tt +47 -0
  143. data/lib/generators/fluxbit/templates/partial.html.erb.tt +61 -0
  144. data/lib/generators/fluxbit/templates/policy.rb.tt +36 -0
  145. data/lib/generators/fluxbit/templates/send_alert_via_drawer.erb.tt +10 -0
  146. data/lib/generators/fluxbit/templates/show.html.erb.tt +44 -0
  147. data/lib/generators/fluxbit/templates/show.json.jbuilder.tt +6 -0
  148. data/lib/generators/fluxbit/templates/update.turbo_stream.erb.tt +10 -0
  149. data/lib/generators/fluxbit/templates/update_all.turbo_stream.erb.tt +20 -0
  150. data/lib/install/install.rb +58 -0
  151. metadata +107 -18
  152. data/app/helpers/fluxbit/classes_helper.rb +0 -9
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Fluxbit::AccordionComponent` renders an accordion component following Flowbite styles.
4
+ # It supports collapsible panels with headers and content, customizable icons, and various styling options.
5
+ class Fluxbit::AccordionComponent < Fluxbit::Component
6
+ include Fluxbit::Config::AccordionComponent
7
+
8
+ renders_many :panels, lambda { |**attrs, &block|
9
+ panel = Panel.new(accordion_id: fx_id, flush: @flush, color: @color, **attrs)
10
+ block.call(panel) if block_given?
11
+ panel
12
+ }
13
+
14
+ ##
15
+ # Initializes the accordion component with the given properties.
16
+ #
17
+ # @param [Hash] **props The properties to customize the accordion.
18
+ # @option props [Boolean] :flush (false) When true, removes borders and rounded corners for seamless integration.
19
+ # @option props [Symbol, String] :color (:default) The color theme for accordion panels (default, light, primary, secondary, success, danger, warning, info, dark).
20
+ # @option props [Boolean] :collapse_all (false) When true, allows all panels to be collapsed simultaneously. When false, keeps at least one panel open.
21
+ # @option props [String] :remove_class ('') CSS classes to remove from the default class list.
22
+ # @option props [Hash] **props Remaining options declared as HTML attributes for the accordion container.
23
+ #
24
+ # @return [Fluxbit::AccordionComponent]
25
+ def initialize(**props)
26
+ super
27
+ @props = props
28
+ @flush = options(@props.delete(:flush), default: @@flush)
29
+ @color = options(@props.delete(:color), collection: styles[:colors].keys, default: @@color).to_sym
30
+ @collapse_all = options(@props.delete(:collapse_all), default: @@collapse_all)
31
+ end
32
+
33
+ def before_render
34
+ add to: @props, first_element: true, class: [
35
+ styles[:base],
36
+ @flush ? "" : "border border-gray-200 dark:border-gray-700 rounded-xl"
37
+ ]
38
+ @props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
39
+ @props[:id] ||= fx_id
40
+ @props["data-accordion"] = @collapse_all ? "collapse" : "open"
41
+ end
42
+
43
+ def call
44
+ tag.div(**@props) do
45
+ safe_join(panels)
46
+ end
47
+ end
48
+
49
+ class Panel < Fluxbit::Component
50
+ include Fluxbit::Config::AccordionComponent
51
+ renders_one :header
52
+ renders_one :body
53
+
54
+ ##
55
+ # Initializes an accordion panel with the given properties.
56
+ #
57
+ # @param [String] accordion_id The parent accordion's ID for proper ARIA relationships.
58
+ # @param [Boolean] flush (false) Whether the panel should use flush styling.
59
+ # @param [Symbol, String] color (:default) The color theme for this panel.
60
+ # @param [Boolean] open (false) Whether the panel should start in an expanded state.
61
+ # @param [Integer] index (0) The panel's position index for proper styling (first, middle, last).
62
+ # @param [Hash] **props Additional HTML attributes for the panel container.
63
+ #
64
+ # @return [Fluxbit::AccordionComponent::Panel]
65
+ def initialize(accordion_id:, **props)
66
+ super
67
+ @props = props
68
+ @accordion_id = accordion_id
69
+ @flush = options(@props.delete(:flush), default: @@flush)
70
+ @color = options(@props.delete(:color), collection: styles[:colors].keys, default: @@color).to_sym
71
+ @open = options(@props.delete(:open), default: false)
72
+ @index = options(@props.delete(:index), default: 0)
73
+ end
74
+
75
+ def call
76
+ header_id = "#{@accordion_id}-header-#{@index}"
77
+ content_id = "#{@accordion_id}-content-#{@index}"
78
+
79
+ header_base = styles[:item][:header][:base]
80
+ content_base = styles[:item][:content][:base]
81
+
82
+ if @flush
83
+ header_base = header_base.gsub(/border[^-]\S*/, "").gsub(/rounded-\S+/, "")
84
+ content_base = content_base.gsub(/border[^-]\S*/, "").gsub(/rounded-\S+/, "")
85
+ end
86
+
87
+ header_classes = [
88
+ header_base,
89
+ styles[:colors][@color][:header],
90
+ (@index == 0) ? styles[:item][:header][:first] : styles[:item][:header][:middle]
91
+ ].compact.join(" ")
92
+
93
+ content_classes = [
94
+ content_base,
95
+ styles[:colors][@color][:content],
96
+ (@index == 0) ? styles[:item][:content][:first] : styles[:item][:content][:middle]
97
+ ].compact.join(" ")
98
+
99
+ tag.div(class: styles[:item][:base]) do
100
+ concat(
101
+ tag.h2(id: header_id) do
102
+ tag.button(
103
+ type: "button",
104
+ class: header_classes,
105
+ "data-accordion-target" => "##{content_id}",
106
+ "aria-expanded" => @open.to_s,
107
+ "aria-controls" => content_id
108
+ ) do
109
+ concat(tag.span(header || "Accordion Header"))
110
+ concat(chevron_down(class: styles[:item][:icon][:base]))
111
+ end
112
+ end
113
+ )
114
+ concat(
115
+ tag.div(
116
+ body || "",
117
+ id: content_id,
118
+ class: [ @open ? "" : "hidden", content_classes ].join(" "),
119
+ "aria-labelledby" => header_id
120
+ )
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
@@ -73,9 +73,9 @@ class Fluxbit::AlertComponent < Fluxbit::Component
73
73
  end
74
74
 
75
75
  def call
76
- content_tag :div, @props do
76
+ tag.div(**@props) do
77
77
  concat @icon
78
- concat content_tag(:div, content, class: "ml-3 text-sm font-medium")
78
+ concat tag.div(content, class: "ml-3 text-sm font-medium")
79
79
  concat close_button
80
80
  end
81
81
  end
@@ -103,24 +103,24 @@ class Fluxbit::AlertComponent < Fluxbit::Component
103
103
  def define_icon(icon, class_icon = "")
104
104
  return "" if icon == false
105
105
  icon = icon || @@icon
106
- return anyicon(icon: icon, class: class_icon) if icon != :default
106
+ return anyicon(icon, class: class_icon) if icon != :default
107
107
 
108
- anyicon(icon: "heroicons_solid:#{styles[:default_icons][@color]}", class: class_icon + " w-4 h-4")
108
+ anyicon("heroicons_solid:#{styles[:default_icons][@color]}", class: class_icon + " size-5")
109
109
  end
110
110
 
111
111
  def close_button
112
112
  return "" unless @can_close
113
113
 
114
114
  b_props = {
115
- "aria-label" => "Close",
115
+ "aria-label" => t("fluxbit.alert.aria_close"),
116
116
  "data-dismiss-target" => "##{@props["id"]}",
117
117
  type: "button"
118
118
  }
119
119
 
120
120
  add to: b_props, class: [ styles[:close_button][:base], styles[:close_button][:colors][@color] ]
121
- content_tag :button, b_props do
122
- concat content_tag(:span, "Dismiss", class: "sr-only")
123
- concat anyicon(icon: "heroicons_outline:x-mark", class: "w-5 h-5")
121
+ tag.button(**b_props) do
122
+ concat tag.span(t("fluxbit.alert.dismiss"), class: "sr-only")
123
+ concat anyicon("heroicons_outline:x-mark", class: "size-5")
124
124
  end
125
125
  end
126
126
  end
@@ -55,11 +55,11 @@ class Fluxbit::AvatarComponent < Fluxbit::Component
55
55
  end
56
56
 
57
57
  def avatar_itself
58
- return content_tag(:img, "", @props) unless @src.nil? || @placeholder_initials
58
+ return tag.img(**@props) unless @src.nil? || @placeholder_initials
59
59
 
60
- content_tag :div, @props do
60
+ tag.div(**@props) do
61
61
  if @placeholder_initials
62
- content_tag :span, @placeholder_initials, class: styles[:initials][:text]
62
+ tag.span(@placeholder_initials, class: styles[:initials][:text])
63
63
  else
64
64
  placeholder_icon
65
65
  end
@@ -80,8 +80,8 @@ class Fluxbit::AvatarComponent < Fluxbit::Component
80
80
  "clip-rule": "evenodd"
81
81
  }
82
82
 
83
- content_tag(:svg, svg_attributes) do
84
- content_tag(:path, "", path_attributes)
83
+ tag.svg(**svg_attributes) do
84
+ tag.path(**path_attributes)
85
85
  end
86
86
  end
87
87
 
@@ -92,20 +92,19 @@ class Fluxbit::AvatarComponent < Fluxbit::Component
92
92
  end
93
93
 
94
94
  def dot_indicator
95
- content_tag :span,
96
- "",
97
- class: [
98
- styles[:status][:base],
99
- styles[:status][:options][@status],
100
- styles[@rounded ? :rounded : :not_rounded][:status_position][@status_position[0]],
95
+ tag.span("", class: [
96
+ styles[:status][:base],
97
+ styles[:status][:options][@status],
98
+ styles[@rounded ? :rounded : :not_rounded][:status_position][@status_position[0]],
101
99
  styles[@rounded ? :rounded : :not_rounded][:status_position][@status_position[1]][@size]
102
100
  ].join(" ")
101
+ )
103
102
  end
104
103
 
105
104
  def call
106
105
  return avatar_itself unless @status
107
106
 
108
- content_tag :div, class: "relative" do
107
+ tag.div(class: "relative") do
109
108
  concat avatar_itself
110
109
  concat dot_indicator
111
110
  end
@@ -11,7 +11,7 @@ class Fluxbit::AvatarGroupComponent < Fluxbit::Component
11
11
  renders_many :gravatars, Fluxbit::GravatarComponent
12
12
 
13
13
  def call
14
- content_tag :div, class: styles[:group] do
14
+ tag.div(class: styles[:group]) do
15
15
  avatars.each do |avatar|
16
16
  concat render(avatar)
17
17
  end
@@ -36,12 +36,13 @@ class Fluxbit::BadgeComponent < Fluxbit::Component
36
36
  def initialize(**props)
37
37
  super
38
38
  @props = props
39
- @color = @props.delete(:color) || @@color
40
- @pill = @props.delete(:pill) || @@pill
41
- @size = @props.delete(:size) || @@size
42
- @perfect_rounded = @props.delete(:perfect_rounded) || @@perfect_rounded
43
- @notification = @props.delete(:notification) || @@notification
44
- @as = @props.delete(:as) || @@as
39
+ @color = options @props.delete(:color), collection: styles[:colors].keys, default: @@color
40
+ @pill = options @props.delete(:pill), default: @@pill
41
+ @size = options @props.delete(:size), default: @@size
42
+ @perfect_rounded = options @props.delete(:perfect_rounded), default: @@perfect_rounded
43
+ @notification = options @props.delete(:notification), default: @@notification
44
+ @as = options @props.delete(:as), default: @@as
45
+
45
46
  add(class: badge_classes, to: @props, first_element: true)
46
47
  @props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
47
48
  end
@@ -63,9 +64,9 @@ class Fluxbit::BadgeComponent < Fluxbit::Component
63
64
  styles[:colors][@color.to_sym],
64
65
  styles[:sizes][@size.to_i],
65
66
  styles[:perfect_rounded][@perfect_rounded.to_i],
67
+ styles[:pill][@pill ? :on : :off],
66
68
  notification_classes
67
69
  ]
68
- base_classes << "rounded-full" if @pill
69
70
  base_classes.join(" ")
70
71
  end
71
72
  end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The `Fluxbit::BannerComponent` is a customizable banner component that extends `Fluxbit::Component`.
5
+ # It provides various options to display banner messages with different styles, positions, and behaviors
6
+ # such as close functionality and call-to-action elements.
7
+ #
8
+ # Example usage:
9
+ # = render Fluxbit::BannerComponent.new(position: :top, color: :info).with_content("Banner message")
10
+ #
11
+ class Fluxbit::BannerComponent < Fluxbit::Component
12
+ include Fluxbit::Config::BannerComponent
13
+
14
+ renders_one :cta_button, lambda { |**props|
15
+ Fluxbit::ButtonComponent.new(**props)
16
+ }
17
+
18
+ renders_one :logo, lambda { |**props|
19
+ content_tag(:img, "", **props)
20
+ }
21
+
22
+ ##
23
+ # Initializes the banner component with the given properties.
24
+ #
25
+ # @param [Hash] props The properties to customize the banner.
26
+ # @option props [Symbol, String] :position (:top) The position of the banner (top, bottom, sticky_top, sticky_bottom).
27
+ # @option props [Symbol, String] :color (:info) The color style of the banner.
28
+ # @option props [Symbol, String, Boolean] :icon (:default) The icon to display in the banner or `false` to omit.
29
+ # @option props [Hash] :icon_html ({}) Additional HTML attributes for the icon.
30
+ # @option props [Boolean] :dismissible (true) Determines if the banner can be dismissed.
31
+ # @option props [Boolean] :full_width (true) Determines if the banner spans the full width.
32
+ # @option props [String] :remove_class ('') Classes to remove from the default class list.
33
+ # @option props [Hash] **props Remaining options declared as HTML attributes.
34
+ #
35
+ # @return [Fluxbit::BannerComponent]
36
+ #
37
+ # @example
38
+ # = render Fluxbit::BannerComponent.new(position: :top, color: :info).with_content("Banner message")
39
+ #
40
+ def initialize(**props)
41
+ super
42
+ @props = props
43
+ @position = define_position(@props.delete(:position) || @@position)
44
+ @color = define_color(@props.delete(:color) || @@color)
45
+ icon_prop = @props.delete(:icon)
46
+ icon_html = @props.delete(:icon_html) || {}
47
+ @icon = define_icon(icon_prop.nil? ? :default : icon_prop, icon_html)
48
+ @dismissible = @props[:dismissible].nil? ? @@dismissible : @props.delete(:dismissible)
49
+ @full_width = @props[:full_width].nil? ? @@full_width : @props.delete(:full_width)
50
+ @props["id"] = fx_id if @props["id"].nil?
51
+
52
+ declare_classes
53
+ @props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
54
+ end
55
+
56
+ def call
57
+ tag.div(**@props) do
58
+ concat banner_content
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def banner_content
65
+ content_wrapper_class = @full_width ? styles[:content_wrapper][:full_width] : styles[:content_wrapper][:constrained]
66
+
67
+ tag.div(class: content_wrapper_class) do
68
+ concat banner_left_content
69
+ concat banner_right_content if @dismissible || cta_button?
70
+ end
71
+ end
72
+
73
+ def banner_left_content
74
+ tag.div(class: styles[:left_content]) do
75
+ concat logo if logo?
76
+ concat @icon unless @icon.blank?
77
+ text_class = icon_or_logo_present? ? styles[:text][:with_icon_or_logo] : styles[:text][:without_icon_or_logo]
78
+ concat tag.div(content, class: text_class)
79
+ end
80
+ end
81
+
82
+ def banner_right_content
83
+ tag.div(class: styles[:right_content]) do
84
+ concat cta_button if cta_button?
85
+ concat dismiss_button if @dismissible
86
+ end
87
+ end
88
+
89
+ def icon_or_logo_present?
90
+ !@icon.blank? || logo?
91
+ end
92
+
93
+ def dismiss_button
94
+ button_props = {
95
+ "aria-label" => t("fluxbit.banner.aria_close"),
96
+ "data-dismiss-target" => "##{@props["id"]}",
97
+ type: "button",
98
+ class: styles[:dismiss_button][:base]
99
+ }
100
+
101
+ if cta_button?
102
+ add(to: button_props, class: styles[:dismiss_button][:with_cta])
103
+ end
104
+
105
+ tag.button(**button_props) do
106
+ concat tag.span(t("fluxbit.banner.dismiss"), class: styles[:screen_reader])
107
+ concat close_icon(class: styles[:close_icon])
108
+ end
109
+ end
110
+
111
+ def declare_classes
112
+ base_classes = [
113
+ styles[:base],
114
+ styles[:positions][@position],
115
+ styles[:colors][@color]
116
+ ]
117
+
118
+ add(to: @props, first_element: true, class: base_classes)
119
+ end
120
+
121
+ def define_position(position)
122
+ position.to_sym.in?(styles[:positions].keys) ? position.to_sym : @@position
123
+ end
124
+
125
+ def define_color(color)
126
+ color.to_sym.in?(styles[:colors].keys) ? color.to_sym : @@color
127
+ end
128
+
129
+ def define_icon(icon, icon_html = {})
130
+ return "" if icon == false
131
+ icon = icon || @@icon
132
+ icon_props = { class: styles[:icon_default] }.merge(icon_html)
133
+ icon_props[:class] = remove_class(icon_props.delete(:remove_class) || "", icon_props[:class])
134
+
135
+ return anyicon(icon, **icon_props) if icon != :default
136
+
137
+ anyicon("heroicons_solid:#{styles[:default_icons][@color]}", **icon_props)
138
+ end
139
+ end