fluxbit_view_components 0.2.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 (173) 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/check_box_component.rb +56 -0
  35. data/app/components/fluxbit/form/component.rb +27 -26
  36. data/app/components/fluxbit/form/dropzone_component.html.erb +39 -0
  37. data/app/components/fluxbit/form/dropzone_component.rb +39 -0
  38. data/app/components/fluxbit/form/field_component.rb +28 -0
  39. data/app/components/fluxbit/form/form_builder_component.rb +1 -1
  40. data/app/components/fluxbit/form/{helper_text_component.rb → help_text_component.rb} +9 -4
  41. data/app/components/fluxbit/form/label_component.rb +40 -30
  42. data/app/components/fluxbit/form/password_component.rb +247 -0
  43. data/app/components/fluxbit/form/radio_group_button_component.rb +126 -0
  44. data/app/components/fluxbit/form/range_component.rb +52 -0
  45. data/app/components/fluxbit/form/select_component.rb +185 -0
  46. data/app/components/fluxbit/form/text_field_component.rb +185 -0
  47. data/app/components/fluxbit/form/toggle_component.html.erb +23 -0
  48. data/app/components/fluxbit/form/toggle_component.rb +81 -0
  49. data/app/components/fluxbit/form/upload_image_component.html.erb +50 -0
  50. data/app/components/fluxbit/form/upload_image_component.rb +61 -0
  51. data/app/components/fluxbit/gravatar_component.rb +7 -0
  52. data/app/components/fluxbit/icon_helpers.rb +167 -0
  53. data/app/components/fluxbit/link_component.rb +42 -0
  54. data/app/components/fluxbit/modal_component.rb +28 -31
  55. data/app/components/fluxbit/pagination_component.rb +206 -0
  56. data/app/components/fluxbit/popover_component.rb +14 -14
  57. data/app/components/fluxbit/progress_component.rb +196 -0
  58. data/app/components/fluxbit/skeleton_component.rb +237 -0
  59. data/app/components/fluxbit/speed_dial_action_component.html.erb +30 -0
  60. data/app/components/fluxbit/speed_dial_action_component.rb +59 -0
  61. data/app/components/fluxbit/speed_dial_component.html.erb +33 -0
  62. data/app/components/fluxbit/speed_dial_component.rb +73 -0
  63. data/app/components/fluxbit/spinner_component.rb +71 -0
  64. data/app/components/fluxbit/spinner_percent_component.rb +174 -0
  65. data/app/components/fluxbit/stepper_component.rb +223 -0
  66. data/app/components/fluxbit/tab_component.rb +44 -25
  67. data/app/components/fluxbit/table_component.rb +186 -0
  68. data/app/components/fluxbit/table_group_component.rb +28 -0
  69. data/app/components/fluxbit/theme_button_component.rb +64 -0
  70. data/app/components/fluxbit/timeline_component.rb +63 -0
  71. data/app/components/fluxbit/timeline_item_component.html.erb +64 -0
  72. data/app/components/fluxbit/timeline_item_component.rb +78 -0
  73. data/app/components/fluxbit/tooltip_component.rb +2 -2
  74. data/app/helpers/fluxbit/components_helper.rb +93 -51
  75. data/app/helpers/fluxbit/form_builder.rb +136 -0
  76. data/app/helpers/fluxbit/view_helper.rb +71 -0
  77. data/config/locales/en.yml +37 -4
  78. data/config/locales/pt-BR.yml +36 -0
  79. data/lib/fluxbit/config/accordion_component.rb +73 -0
  80. data/lib/fluxbit/config/avatar_component.rb +11 -11
  81. data/lib/fluxbit/config/badge_component.rb +14 -11
  82. data/lib/fluxbit/config/banner_component.rb +60 -0
  83. data/lib/fluxbit/config/bottom_navigation_component.rb +74 -0
  84. data/lib/fluxbit/config/breadcrumb_component.rb +24 -0
  85. data/lib/fluxbit/config/button_component.rb +6 -4
  86. data/lib/fluxbit/config/card_component.rb +23 -12
  87. data/lib/fluxbit/config/carousel_component.rb +33 -0
  88. data/lib/fluxbit/config/drawer_component.rb +48 -0
  89. data/lib/fluxbit/config/dropdown_component.rb +29 -0
  90. data/lib/fluxbit/config/form/check_box_component.rb +19 -0
  91. data/lib/fluxbit/config/form/dropzone_component.rb +20 -0
  92. data/lib/fluxbit/config/form/{helper_text_component.rb → help_text_component.rb} +2 -2
  93. data/lib/fluxbit/config/form/label_component.rb +31 -0
  94. data/lib/fluxbit/config/form/password_component.rb +19 -0
  95. data/lib/fluxbit/config/form/radio_group_button_component.rb +24 -0
  96. data/lib/fluxbit/config/form/range_component.rb +15 -0
  97. data/lib/fluxbit/config/form/text_field_component.rb +76 -0
  98. data/lib/fluxbit/config/form/toggle_component.rb +79 -0
  99. data/lib/fluxbit/config/link_component.rb +24 -0
  100. data/lib/fluxbit/config/modal_component.rb +1 -1
  101. data/lib/fluxbit/config/pagination_component.rb +31 -0
  102. data/lib/fluxbit/config/popover_component.rb +1 -1
  103. data/lib/fluxbit/config/progress_component.rb +63 -0
  104. data/lib/fluxbit/config/skeleton_component.rb +82 -0
  105. data/lib/fluxbit/config/speed_dial_component.rb +50 -0
  106. data/lib/fluxbit/config/spinner_component.rb +30 -0
  107. data/lib/fluxbit/config/spinner_percent_component.rb +61 -0
  108. data/lib/fluxbit/config/stepper_component.rb +299 -0
  109. data/lib/fluxbit/config/tab_component.rb +6 -0
  110. data/lib/fluxbit/config/table_component.rb +75 -0
  111. data/lib/fluxbit/config/theme_button_component.rb +19 -0
  112. data/lib/fluxbit/config/timeline_component.rb +77 -0
  113. data/lib/fluxbit/view_components/engine.rb +11 -3
  114. data/lib/fluxbit/view_components/version.rb +1 -1
  115. data/lib/fluxbit/view_components.rb +27 -1
  116. data/lib/generators/fluxbit/devise_views_generator.rb +116 -0
  117. data/lib/generators/fluxbit/pagy_generator.rb +39 -0
  118. data/lib/generators/fluxbit/scaffold_generator.rb +165 -0
  119. data/lib/generators/fluxbit/templates/_alert.html.erb.tt +1 -0
  120. data/lib/generators/fluxbit/templates/_flash.html.erb.tt +15 -0
  121. data/lib/generators/fluxbit/templates/_form.html.erb.tt +38 -0
  122. data/lib/generators/fluxbit/templates/_metadata.html.erb.tt +44 -0
  123. data/lib/generators/fluxbit/templates/controller.rb.tt +406 -0
  124. data/lib/generators/fluxbit/templates/create.turbo_stream.erb.tt +7 -0
  125. data/lib/generators/fluxbit/templates/destroy.turbo_stream.erb.tt +3 -0
  126. data/lib/generators/fluxbit/templates/destroy_all.turbo_stream.erb.tt +9 -0
  127. data/lib/generators/fluxbit/templates/devise_views/confirmations/new.html.erb +11 -0
  128. data/lib/generators/fluxbit/templates/devise_views/layouts/devise.html.erb +64 -0
  129. data/lib/generators/fluxbit/templates/devise_views/mailer/confirmation_instructions.html.erb +5 -0
  130. data/lib/generators/fluxbit/templates/devise_views/mailer/email_changed.html.erb +7 -0
  131. data/lib/generators/fluxbit/templates/devise_views/mailer/password_changed.html.erb +3 -0
  132. data/lib/generators/fluxbit/templates/devise_views/mailer/reset_password_instructions.html.erb +8 -0
  133. data/lib/generators/fluxbit/templates/devise_views/mailer/unlock_instructions.html.erb +7 -0
  134. data/lib/generators/fluxbit/templates/devise_views/passwords/edit.html.erb +29 -0
  135. data/lib/generators/fluxbit/templates/devise_views/passwords/new.html.erb +11 -0
  136. data/lib/generators/fluxbit/templates/devise_views/registrations/edit.html.erb +43 -0
  137. data/lib/generators/fluxbit/templates/devise_views/registrations/new.html.erb +34 -0
  138. data/lib/generators/fluxbit/templates/devise_views/sessions/new.html.erb +15 -0
  139. data/lib/generators/fluxbit/templates/devise_views/shared/_error_messages.html.erb +14 -0
  140. data/lib/generators/fluxbit/templates/devise_views/shared/_links.html.erb +25 -0
  141. data/lib/generators/fluxbit/templates/devise_views/unlocks/new.html.erb +11 -0
  142. data/lib/generators/fluxbit/templates/edit.html.erb.tt +47 -0
  143. data/lib/generators/fluxbit/templates/fluxbit_pagy.css +27 -0
  144. data/lib/generators/fluxbit/templates/i18n.en.yml.tt +121 -0
  145. data/lib/generators/fluxbit/templates/i18n.pt-BR.yml.tt +121 -0
  146. data/lib/generators/fluxbit/templates/index.html.erb.tt +254 -0
  147. data/lib/generators/fluxbit/templates/index.json.jbuilder.tt +33 -0
  148. data/lib/generators/fluxbit/templates/new.html.erb.tt +47 -0
  149. data/lib/generators/fluxbit/templates/partial.html.erb.tt +61 -0
  150. data/lib/generators/fluxbit/templates/policy.rb.tt +36 -0
  151. data/lib/generators/fluxbit/templates/send_alert_via_drawer.erb.tt +10 -0
  152. data/lib/generators/fluxbit/templates/show.html.erb.tt +44 -0
  153. data/lib/generators/fluxbit/templates/show.json.jbuilder.tt +6 -0
  154. data/lib/generators/fluxbit/templates/update.turbo_stream.erb.tt +10 -0
  155. data/lib/generators/fluxbit/templates/update_all.turbo_stream.erb.tt +20 -0
  156. data/lib/install/install.rb +61 -3
  157. metadata +127 -35
  158. data/LICENSE.txt +0 -20
  159. data/app/components/fluxbit/form/checkbox_input_component.rb +0 -61
  160. data/app/components/fluxbit/form/datepicker_component.rb +0 -7
  161. data/app/components/fluxbit/form/radio_input_component.rb +0 -21
  162. data/app/components/fluxbit/form/range_input_component.rb +0 -51
  163. data/app/components/fluxbit/form/select_free_input_component.rb +0 -77
  164. data/app/components/fluxbit/form/select_input_component.rb +0 -21
  165. data/app/components/fluxbit/form/spacer_input_component.rb +0 -12
  166. data/app/components/fluxbit/form/text_input_component.rb +0 -225
  167. data/app/components/fluxbit/form/textarea_input_component.rb +0 -57
  168. data/app/components/fluxbit/form/toggle_input_component.rb +0 -166
  169. data/app/components/fluxbit/form/upload_image_input_component.html.erb +0 -48
  170. data/app/components/fluxbit/form/upload_image_input_component.rb +0 -61
  171. data/app/components/fluxbit/form/upload_input_component.html.erb +0 -12
  172. data/app/components/fluxbit/form/upload_input_component.rb +0 -47
  173. data/app/helpers/fluxbit/classes_helper.rb +0 -9
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The `Fluxbit::TableComponent` is a customizable alert component that extends `Fluxbit::Component`.
5
+ # It provides various options to display alert messages with different styles, icons, and behaviors
6
+ # such as close functionality and animations.
7
+ #
8
+ # Example usage:
9
+ # = render Fluxbit::TableComponent.new(
10
+ # striped: true,
11
+ # bordered: true,
12
+ # hover: true,
13
+ # shadow: true,
14
+ # wrapper_html: { class: "my-custom-wrapper" },
15
+ # thead_html: { class: "my-custom-thead" },
16
+ # tbody_html: { class: "my-custom-tbody" },
17
+ # tr_html: { class: "my-custom-tr" }
18
+ # ) do |table|
19
+ # table.with_header do |header|
20
+ # header.with_column "Column 1"
21
+ # header.with_column "Column 2"
22
+ # end
23
+ #
24
+ # table.with_row do |row|
25
+ # row.with_cell "Data 1"
26
+ # row.with_cell "Data 2"
27
+ # end
28
+ # end
29
+ #
30
+ class Fluxbit::TableComponent < Fluxbit::Component
31
+ include Fluxbit::Config::TableComponent
32
+
33
+ renders_one :footer, lambda { |*args, **props, &block|
34
+ add class: styles[:footer][:base], to: props
35
+ props[:as] ||= :tr
36
+ remove_class_from_props props
37
+
38
+ props[:cells_html] ||= {}
39
+ props[:cells_html][:as] ||= :td
40
+ props[:cells_html][:scope] ||= :row
41
+ add(class: styles[:footer][:cell], to: props[:cells_html])
42
+ remove_class_from_props props[:cells_html]
43
+
44
+ Fluxbit::TableGroupComponent.new(*args, **props, &block)
45
+ }
46
+
47
+ renders_many :headers, lambda { |*args, **props, &block|
48
+ add class: styles[:head][:base], to: props
49
+ props[:as] ||= :tr
50
+ remove_class_from_props props
51
+
52
+ props[:cells_html] ||= {}
53
+ props[:cells_html][:as] ||= :th
54
+ props[:cells_html][:scope] ||= :col
55
+ add(class: styles[:head][:cell], to: props[:cells_html])
56
+ remove_class_from_props props[:cells_html]
57
+
58
+ Fluxbit::TableGroupComponent.new(*args, **props, &block)
59
+ }
60
+
61
+ renders_many :rows, lambda { |*args, **props, &block|
62
+ color = props.delete(:color) || :default
63
+ add(class: styles[:row][:base], to: props)
64
+ add(class: styles[:row][:striped][color], to: props) if @striped
65
+ add(class: styles[:row][:hovered][color], to: props) if @hover
66
+ add(class: styles[:row][:bordered], to: props) if @bordered
67
+ add(class: styles[:row][:colors][color], to: props, first_element: true) if styles[:row][:colors].key?(color) && !@striped
68
+ props[:as] ||= :tr
69
+ remove_class_from_props props
70
+
71
+ props[:cells_html] ||= {}
72
+ props[:cells_html][:as] ||= :td
73
+ props[:cells_html][:scope] ||= :row
74
+ add(class: styles[:row][:cell][:base], to: props[:cells_html])
75
+ remove_class_from_props props[:cells_html]
76
+
77
+ Fluxbit::TableGroupComponent.new(*args, **props, &block)
78
+ }
79
+
80
+ ##
81
+ # Initializes the table component with the given properties.
82
+ #
83
+ # @param [Hash] props The properties to customize the table.
84
+ # @option props [Boolean] :striped (false) Determines if the table rows should be striped.
85
+ # @option props [Boolean] :bordered (false) Determines if the table should have borders.
86
+ # @option props [Boolean] :hover (false) Determines if the table rows should highlight on hover.
87
+ # @option props [Boolean] :shadow (false) Determines if the table should have a shadow effect.
88
+ # @option props [Hash] :thead_html Additional HTML attributes for the table header.
89
+ # @option props [Hash] :tbody_html Additional HTML attributes for the table body.
90
+ # @option props [Hash] :tr_html Additional HTML attributes for the table rows.
91
+ # @option props [Hash] :cells_html Additional HTML attributes for the table cells.
92
+ #
93
+ # @example
94
+ # = render Fluxbit::TableComponent.new(
95
+ # striped: true,
96
+ # bordered: true,
97
+ # hover: true,
98
+ # shadow: true,
99
+ # thead_html: { class: "my-custom-thead" },
100
+ # tbody_html: { class: "my-custom-tbody" },
101
+ # tr_html: { class: "my-custom-tr" }
102
+ # ) do |table|
103
+ # table.with_header do |header|
104
+ # header.with_column "Column 1"
105
+ # header.with_column "Column 2"
106
+ # end
107
+ #
108
+ # table.with_row do |row|
109
+ # row.with_cell "Data 1"
110
+ # row.with_cell "Data 2"
111
+ # end
112
+ # end
113
+ #
114
+ # @return [Fluxbit::TableComponent]
115
+ #
116
+ def initialize(**props)
117
+ super
118
+ @props = props
119
+ @striped = options @props.delete(:striped), default: @@striped
120
+ @bordered = options @props.delete(:bordered), default: @@bordered
121
+ @hover = options @props.delete(:hover), default: @@hover
122
+ @shadow = options @props.delete(:shadow), default: @@shadow
123
+ @only_rows = options @props.delete(:only_rows), default: false
124
+
125
+ # Wrapper HTML
126
+ @wrapper_html = @props.delete(:wrapper_html) || {}
127
+ add(class: styles[:wrapper][:base], to: @wrapper_html)
128
+ add(class: styles[:wrapper][:shadow], to: @wrapper_html) if @shadow
129
+
130
+ # Head HTML
131
+ @thead_html = @props.delete(:thead_html) || {}
132
+ # add(class: styles[:head][:base], to: @thead_html)
133
+
134
+ # Body HTML
135
+ @tbody_html = @props.delete(:tbody_html) || {}
136
+ add(class: styles[:body][:base], to: @tbody_html)
137
+
138
+ # Row HTML
139
+ @tr_html = @props.delete(:tr_html) || {}
140
+ add(class: styles[:body][:base], to: @tbody_html)
141
+
142
+ # Footer HTML
143
+ @tfoot_html = @props.delete(:tfoot_html) || {}
144
+ add(class: styles[:footer][:base], to: @tfoot_html)
145
+
146
+ # Table HTML
147
+ add(class: styles[:root][:base], to: @props)
148
+ end
149
+
150
+ def call
151
+ return safe_join(rows) if @only_rows && rows?
152
+
153
+ # Wrapper
154
+ capture do
155
+ # Table
156
+ concat(tag.div(**@wrapper_html) do
157
+ concat(tag.table(**@props) do
158
+ # header
159
+ concat(
160
+ tag.thead(**@thead_html) do
161
+ concat(safe_join headers)
162
+ end
163
+ ) if headers?
164
+
165
+ # body
166
+ concat(
167
+ tag.tbody(**@tbody_html) do
168
+ if content.present?
169
+ content
170
+ else
171
+ concat(safe_join(rows)) if rows?
172
+ end
173
+ end
174
+ )
175
+
176
+ # Footer
177
+ concat(
178
+ tag.tfoot(**@tfoot_html) do
179
+ concat(footer)
180
+ end
181
+ ) if footer?
182
+ end)
183
+ end)
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fluxbit::TableGroupComponent < Fluxbit::Component
4
+ include Fluxbit::Config::TableComponent
5
+
6
+ renders_many :cells, lambda { |*args, **props, &block|
7
+ cells_html_now = @cells_html.dup
8
+ add(class: props[:class], to: cells_html_now)
9
+ cells_html_now = props.merge cells_html_now
10
+ add(class: styles[:row][:cell][:selected], to: cells_html_now) if props.delete(:selected) || false
11
+ remove_class_from_props(cells_html_now)
12
+
13
+ content_tag(@as_cells, block.call, **cells_html_now)
14
+ }
15
+
16
+ def initialize(**props)
17
+ super
18
+ @props = props
19
+ @as = @props.delete(:as) || :tr
20
+ @cells_html = @props.delete(:cells_html) || { as: :td }
21
+
22
+ @as_cells = @cells_html.delete(:as) || :td
23
+ end
24
+
25
+ def call
26
+ tag.tr(safe_join(cells), **@props)
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The `Fluxbit::ThemeButtonComponent` is a round button component that toggles between dark, light, and system themes.
5
+ # It extends `Fluxbit::ButtonComponent` and automatically updates the theme when clicked.
6
+ class Fluxbit::ThemeButtonComponent < Fluxbit::ButtonComponent
7
+ include Fluxbit::Config::ThemeButtonComponent
8
+
9
+ ##
10
+ # Initializes the theme button component with the given properties.
11
+ #
12
+ # @param [Hash] props The properties to customize the theme button.
13
+ # @option props [Symbol, String] :color The color style of the button (default: :transparent)
14
+ # @option props [Symbol, String] :size The size of the button (default: 2)
15
+ # @option props [Boolean] :pill (true) Makes the button round
16
+ # @option props [Hash] **props Remaining options declared as HTML attributes.
17
+ #
18
+ # @return [Fluxbit::ThemeButtonComponent]
19
+ def initialize(**props)
20
+ # Set default values specific to theme button
21
+ props[:pill] = true unless props.key?(:pill)
22
+ props[:color] ||= :transparent
23
+ props[:size] ||= 2
24
+ props[:remove_dropdown_arrow] = true
25
+
26
+ # Add Stimulus controller
27
+ props["data-controller"] = [props["data-controller"], "fx-theme-button"].compact.join(" ")
28
+ props["data-action"] = [props["data-action"], "click->fx-theme-button#toggle"].compact.join(" ")
29
+
30
+ super(**props)
31
+ end
32
+
33
+ def call
34
+ concat(render_theme_button)
35
+ concat(render_popover_or_tooltip.to_s)
36
+ end
37
+
38
+ private
39
+
40
+ def render_theme_button
41
+ button_content = safe_join([
42
+ content_tag(:span, light_icon, class: "fx-theme-icon fx-theme-light hidden", "data-fx-theme-button-target": "lightIcon"),
43
+ content_tag(:span, dark_icon, class: "fx-theme-icon fx-theme-dark hidden", "data-fx-theme-button-target": "darkIcon"),
44
+ content_tag(:span, system_icon, class: "fx-theme-icon fx-theme-system hidden", "data-fx-theme-button-target": "systemIcon")
45
+ ])
46
+
47
+ content_tag(@as, button_content, @props)
48
+ end
49
+
50
+ def light_icon
51
+ # Sun icon for light mode
52
+ anyicon("heroicons_outline:sun", class: "size-5")
53
+ end
54
+
55
+ def dark_icon
56
+ # Moon icon for dark mode
57
+ anyicon("heroicons_outline:moon", class: "size-5")
58
+ end
59
+
60
+ def system_icon
61
+ # Computer/display icon for system mode
62
+ anyicon("heroicons_outline:computer-desktop", class: "size-5")
63
+ end
64
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Fluxbit::TimelineComponent` is a component for rendering customizable timelines.
4
+ class Fluxbit::TimelineComponent < Fluxbit::Component
5
+ include Fluxbit::Config::TimelineComponent
6
+
7
+ # Slot for timeline items
8
+ renders_many :items, lambda { |**props|
9
+ Fluxbit::TimelineItemComponent.new(
10
+ variant: @variant,
11
+ **props
12
+ )
13
+ }
14
+
15
+ # Initializes the Timeline component with various customization options.
16
+ #
17
+ # @param [Hash] **props The properties to customize the timeline.
18
+ # @option props [Symbol] :variant (:default) The timeline variant (:default, :vertical, :stepper, :activity).
19
+ # @option props [Symbol] :position (:left) The position of timeline indicators (:left, :center, :right).
20
+ # @option props [String] :remove_class ('') CSS classes to remove from the default class list.
21
+ # @option props [Hash] **props Remaining options declared as HTML attributes.
22
+ #
23
+ # @return [Fluxbit::TimelineComponent]
24
+ def initialize(**props)
25
+ super
26
+ @props = props
27
+
28
+ @variant = options @props.delete(:variant), collection: styles[:variants].keys, default: @@variant
29
+ @position = options @props.delete(:position), collection: styles[:positions].keys, default: @@position
30
+
31
+ add class: [
32
+ styles[:base],
33
+ styles[:variants][@variant],
34
+ styles[:positions][@position]
35
+ ], to: @props
36
+
37
+ remove_class_from_props(@props)
38
+ end
39
+
40
+ def call
41
+ content_tag(@variant == :stepper ? "ol" : "ul", **@props) do
42
+ items_with_position.join.html_safe
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def items_with_position
49
+ items.map.with_index do |item, index|
50
+ item.instance_variable_set(:@is_last, index == items.length - 1)
51
+ item
52
+ end
53
+ end
54
+
55
+
56
+ def timeline_classes
57
+ [
58
+ styles[:base],
59
+ styles[:variants][@variant],
60
+ styles[:positions][@position]
61
+ ].compact.join(" ")
62
+ end
63
+ end
@@ -0,0 +1,64 @@
1
+ <li <%= tag.attributes(**@props) %>>
2
+ <% if @variant == :activity %>
3
+ <div class="<%= styles[:activity][:indicator] %>"></div>
4
+ <div class="<%= styles[:activity][:base] %>">
5
+ <% if @date %>
6
+ <time class="<%= styles[:activity][:time] %>"><%= @date %></time>
7
+ <% end %>
8
+ <% if render_as_link? %>
9
+ <a href="<%= @href %>" class="<%= styles[:activity][:title] %>"><%= @title %></a>
10
+ <% else %>
11
+ <h3 class="<%= styles[:activity][:title] %>"><%= @title %></h3>
12
+ <% end %>
13
+ <% if @description %>
14
+ <p class="<%= styles[:activity][:description] %>"><%= @description %></p>
15
+ <% end %>
16
+ </div>
17
+ <% elsif @variant == :stepper %>
18
+ <div class="<%= styles[:stepper][:indicator_container] %>">
19
+ <div class="<%= stepper_indicator_classes %>">
20
+ <% if @icon %>
21
+ <%= icon(@icon, class: (@status == :completed || @status == :current) ? styles[:stepper][:icon_completed] : styles[:stepper][:icon]) %>
22
+ <% end %>
23
+ </div>
24
+ <% unless is_last? %>
25
+ <div class="<%= styles[:stepper][:connector] %>"></div>
26
+ <% end %>
27
+ </div>
28
+ <div class="<%= styles[:stepper][:content] %>">
29
+ <% if @title %>
30
+ <h3 class="<%= styles[:stepper][:title] %>"><%= @title %></h3>
31
+ <% end %>
32
+ <% if @date %>
33
+ <time class="<%= styles[:stepper][:description] %>"><%= @date %></time>
34
+ <% end %>
35
+ <% if @description %>
36
+ <p class="<%= styles[:stepper][:description_paragraph] %>"><%= @description %></p>
37
+ <% end %>
38
+ </div>
39
+ <% else %>
40
+ <!-- Default and vertical variants -->
41
+ <div class="<%= indicator_classes %>">
42
+ <% if @icon %>
43
+ <%= icon(@icon, class: styles[:item][:icon]) %>
44
+ <% else %>
45
+ <div class="<%= styles[:item][:dot] %>"></div>
46
+ <% end %>
47
+ </div>
48
+ <div class="<%= styles[:item][:content][:base] %>">
49
+ <% if @date %>
50
+ <time class="<%= styles[:item][:content][:date] %>"><%= @date %></time>
51
+ <% end %>
52
+ <% if @title %>
53
+ <% if render_as_link? %>
54
+ <a href="<%= @href %>" class="<%= styles[:item][:content][:title] %>"><%= @title %></a>
55
+ <% else %>
56
+ <h3 class="<%= styles[:item][:content][:title] %>"><%= @title %></h3>
57
+ <% end %>
58
+ <% end %>
59
+ <% if @description %>
60
+ <p class="<%= styles[:item][:content][:description] %>"><%= @description %></p>
61
+ <% end %>
62
+ </div>
63
+ <% end %>
64
+ </li>
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Fluxbit::TimelineItemComponent` is a component for rendering individual timeline items.
4
+ class Fluxbit::TimelineItemComponent < Fluxbit::Component
5
+ include Fluxbit::Config::TimelineComponent
6
+
7
+ # Initializes the TimelineItem component.
8
+ #
9
+ # @param [Hash] **props The properties to customize the timeline item.
10
+ # @option props [Symbol] :variant (nil) The timeline variant from parent component.
11
+ # @option props [String] :title (nil) The title of the timeline item.
12
+ # @option props [String] :description (nil) The description text.
13
+ # @option props [String] :date (nil) The date or time for the item.
14
+ # @option props [String, Symbol] :icon (nil) The icon to display in the timeline indicator.
15
+ # @option props [Symbol] :status (:default) The status of the item (:default, :completed, :current, :pending).
16
+ # @option props [Symbol] :color (:blue) The color theme (:blue, :green, :red, :yellow, :purple, :indigo).
17
+ # @option props [String] :href (nil) URL to make the item clickable.
18
+ # @option props [Symbol] :ring (:default) The ring size around indicator (:none, :small, :default, :large).
19
+ # @option props [String] :remove_class ('') CSS classes to remove from the default class list.
20
+ # @option props [Hash] **props Remaining options declared as HTML attributes.
21
+ #
22
+ # @return [Fluxbit::TimelineItemComponent]
23
+ def initialize(**props)
24
+ super
25
+ @props = props
26
+
27
+ @variant = @props.delete(:variant) || :default
28
+ @title = @props.delete(:title)
29
+ @description = @props.delete(:description)
30
+ @date = @props.delete(:date)
31
+ @icon = @props.delete(:icon)
32
+ @status = options @props.delete(:status), collection: styles[:item][:indicator][:status].keys, default: :default
33
+ @color = options @props.delete(:color), collection: styles[:item][:indicator][:colors].keys, default: :blue
34
+ @ring = options @props.delete(:ring), collection: styles[:item][:indicator][:rings].keys, default: :default
35
+ @href = @props.delete(:href)
36
+
37
+ if @variant != :stepper
38
+ add class: styles[:item][:base], to: @props
39
+ end
40
+
41
+ remove_class_from_props(@props)
42
+ end
43
+
44
+ def before_render
45
+ super
46
+ if @variant == :stepper
47
+ # Add stepper classes after @is_last is set
48
+ add class: styles[:stepper][:item], to: @props
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def indicator_classes
55
+ [
56
+ styles[:item][:indicator][:base],
57
+ styles[:item][:indicator][:status][@status],
58
+ styles[:item][:indicator][:colors][@color],
59
+ styles[:item][:indicator][:rings][@ring]
60
+ ].compact.join(" ")
61
+ end
62
+
63
+ def render_as_link?
64
+ @href.present?
65
+ end
66
+
67
+ def is_last?
68
+ @is_last || false
69
+ end
70
+
71
+ def stepper_indicator_classes
72
+ if @status == :completed || @status == :current
73
+ styles[:stepper][:indicator_completed]
74
+ else
75
+ styles[:stepper][:indicator]
76
+ end
77
+ end
78
+ end
@@ -24,7 +24,7 @@ class Fluxbit::TooltipComponent < Fluxbit::Component
24
24
  end
25
25
 
26
26
  def call
27
- content_tag(:div, @props) do
27
+ tag.div(**@props) do
28
28
  concat content
29
29
  concat arrow
30
30
  end
@@ -33,6 +33,6 @@ class Fluxbit::TooltipComponent < Fluxbit::Component
33
33
  def arrow
34
34
  return "" unless @has_arrow
35
35
 
36
- content_tag :div, "", "data-popper-arrow" => true, class: "tooltip-arrow"
36
+ tag.div("", "data-popper-arrow" => true, class: "tooltip-arrow")
37
37
  end
38
38
  end
@@ -3,37 +3,107 @@
3
3
  module Fluxbit
4
4
  module ComponentsHelper
5
5
  # Components
6
- def fx_avatar(...) = fluxbit_method("Avatar", ...)
7
- def fx_avatar_group(...) = fluxbit_method("AvatarGroup", ...)
8
- def fx_gravatar(...) = fluxbit_method("Gravatar", ...)
9
- def fx_alert(...) = fluxbit_method("Alert", ...)
10
- def fx_button(...) = fluxbit_method("Button", ...)
11
- def fx_button_group(...) = fluxbit_method("ButtonGroup", ...)
12
- def fx_badge(...) = fluxbit_method("Badge", ...)
13
- def fx_card(...) = fluxbit_method("Card", ...)
14
- def fx_modal(...) = fluxbit_method("Modal", ...)
15
- def fx_popover(...) = fluxbit_method("Popover", ...)
16
- def fx_tooltip(...) = fluxbit_method("Tooltip", ...)
17
- def fx_flex(...) = fluxbit_method("Flex", ...)
18
- def fx_tab(...) = fluxbit_method("Tab", ...)
6
+ [ :accordion, :avatar, :avatar_group, :banner, :bottom_navigation, :breadcrumb, :gravatar, :alert, :button, :button_group, :link,
7
+ :badge, :carousel, :drawer, :dropdown, :card, :modal, :pagination, :popover, :tooltip, :flex, :tab, :table, :skeleton, :stepper, :speed_dial, :theme_button, :timeline, :spinner_percent, :progress ].each do |component|
8
+ define_method("fx_#{component}") do |*args, **kwargs, &block|
9
+ fluxbit_method(component.to_s.camelize, *args, **kwargs, &block)
10
+ end
11
+ end
19
12
 
20
13
  # Forms
21
- def fx_helper_text(...) = fluxbit_method("Form::HelperText", ...)
22
- def fx_checkbox_input(...) = fluxbit_method("Form::CheckboxInput", ...)
23
- def fx_form_builder(...) = fluxbit_method("Form::FormBuilder", ...)
24
- def fx_label(...) = fluxbit_method("Form::Label", ...)
25
- def fx_range_input(...) = fluxbit_method("Form::RangeInput", ...)
26
- def fx_select_input(...) = fluxbit_method("Form::SelectInput", ...)
27
- def fx_select_free_input(...) = fluxbit_method("Form::SelectFreeInput", ...)
28
- def fx_text_input(...) = fluxbit_method("Form::TextInput", ...)
29
- def fx_textarea_input(...) = fluxbit_method("Form::TextareaInput", ...)
30
- def fx_toggle_input(...) = fluxbit_method("Form::ToggleInput", ...)
14
+ [ :help_text, :form_builder, :label, :range,
15
+ :toggle, :upload_image, :dropzone, :radio_group_button ].each do |component|
16
+ define_method("fx_#{component}") do |*args, **kwargs, &block|
17
+ fluxbit_method("Form::#{component.to_s.camelize}", *args, **kwargs, &block)
18
+ end
19
+ end
20
+
21
+ # Mimics Rails' select_tag signature:
22
+ # select_tag(name, option_tags = nil, options = {})
23
+ #
24
+ # @param name [String, Symbol] Name attribute for the select tag
25
+ # @param option_tags [String, Array, Hash, nil] Pre-formatted HTML or raw options data
26
+ # @param options [Hash] HTML attributes and select options (prompt, class, etc.)
27
+ # @param block [Proc] Optional block for custom options
28
+ #
29
+ # @example Basic usage
30
+ # fx_select "role", ["Admin", "User", "Guest"]
31
+ #
32
+ # @example With options
33
+ # fx_select "country", countries, prompt: "Select a country", class: "custom-select"
34
+ #
35
+ # @example With pre-formatted options
36
+ # fx_select "status", options_for_select(statuses, selected: "active")
37
+ #
38
+ # @example Named parameters (backwards compatible)
39
+ # fx_select name: "role", options: ["Admin", "User"], prompt: "Choose..."
40
+ def fx_select(name = nil, option_tags = nil, options = {}, &block)
41
+ # Handle both positional and named parameters
42
+ if name.is_a?(Hash)
43
+ # Named parameters: fx_select(name: "role", options: [...], prompt: "...")
44
+ options = name
45
+ name = options.delete(:name)
46
+ option_tags = options.delete(:options) || options.delete(:choices)
47
+ elsif option_tags.is_a?(Hash) && options.empty?
48
+ # Two args with second being options: fx_select("role", prompt: "...")
49
+ options = option_tags
50
+ option_tags = options.delete(:options) || options.delete(:choices)
51
+ end
52
+
53
+ # Set the options/choices
54
+ options[:options] = option_tags if option_tags.present?
55
+ options[:name] = name if name.present?
56
+
57
+ fluxbit_method("Form::Select", **options, &block)
58
+ end
31
59
  def form_builder(...) = fluxbit_method("Form::FormBuilder", ...)
32
60
 
61
+ Fluxbit::Form::TextFieldComponent::TYPE_OPTIONS.each do |type|
62
+ define_method(type.in?([ :text_area, :textarea ]) ? "fx_#{type}" : "fx_#{type}_field") do |*args, **kwargs, &block|
63
+ fluxbit_method("Form::TextField", *args, type: type, **kwargs, &block)
64
+ end
65
+ end
66
+
67
+ Fluxbit::Form::CheckBoxComponent::TYPE_OPTIONS.each do |type|
68
+ define_method("fx_#{type}") do |*args, **kwargs, &block|
69
+ fluxbit_method("Form::CheckBox", *args, type: type, **kwargs, &block)
70
+ end
71
+ end
72
+
33
73
  # Typography
34
74
  def fx_heading(...) = fluxbit_method("Heading", ...)
35
75
  def fx_txt(...) = fluxbit_method("Text", ...)
36
76
 
77
+
78
+ def fx_link_to_old(body, url, **kwargs, &block)
79
+ kwargs[:body] = body if body.is_a?(String)
80
+ kwargs[:url] = url if url.is_a?(String)
81
+ kwargs[:with_content] = block if block_given?
82
+ Fluxbit::TextComponent.new(body, url, **kwargs).render(&block)
83
+ end
84
+
85
+ def fx_link_to(body = nil, url = nil, **kwargs, &block)
86
+ kwargs[:as] = :a
87
+ if block_given? && body.is_a?(String) && (url.is_a?(String) || url.is_a?(Symbol))
88
+ # Handle case: fx_link_to("Link text", "/path") { ... }
89
+ kwargs[:href] = url
90
+ fluxbit_method("Link", as: :a, content: body, **kwargs, &block)
91
+ elsif block_given? && body.is_a?(String) && url.nil?
92
+ # Handle case: fx_link_to("/path") { ... content ... }
93
+ kwargs[:href] = body # first arg is URL
94
+ fluxbit_method("Link", as: :a, **kwargs, &block)
95
+ elsif !block_given? && body.is_a?(String) && (url.is_a?(String) || url.is_a?(Symbol))
96
+ # Handle case: fx_link_to("Link text", "/path", class: "...")
97
+ component_klass = "Fluxbit::LinkComponent".constantize
98
+ kwargs[:href] = url
99
+ render(component_klass.new(**kwargs).with_content(body))
100
+ else
101
+ # Handle other cases or provide feedback
102
+ raise ArgumentError, "Invalid arguments for fx_link_to"
103
+ end
104
+ end
105
+
106
+
37
107
  def fluxbit_method(method_name, *args, **kwargs, &c)
38
108
  component_klass = "Fluxbit::#{method_name}Component".constantize
39
109
  if kwargs[:with_content]
@@ -43,33 +113,5 @@ module Fluxbit
43
113
  render(component_klass.new(*args, **kwargs), &c)
44
114
  end
45
115
  end
46
-
47
- # # Succint method for render component
48
- # # from: https://dev.to/abeidahmed/advanced-viewcomponent-patterns-in-rails-2b4m
49
- # #
50
- # # Instead of using 'render XComponent'
51
- # # One can use 'render_component "X", **@options'
52
- # def render_component(component_path, collection: nil, with_content: nil, **options, &block)
53
- # component_klass = "#{component_path.classify}Component".constantize
54
-
55
- # return render component_klass.new(**options).with_content(with_content) if with_content
56
-
57
- # if collection
58
- # render component_klass.with_collection(collection, **options), &block
59
- # else
60
- # render component_klass.new(**options), &block
61
- # end
62
- # end
63
-
64
- # def method_missing(name, *args, &block)
65
- # if name == :alert
66
- # render component_klass.new(**options), &block
67
- # # do something if the method name is "custom_helper_method"
68
- # #content_tag(:div, *args, &block)
69
- # else
70
- # # call the original method_missing method if the method name is not recognized
71
- # super
72
- # end
73
- # end
74
116
  end
75
117
  end