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
@@ -24,16 +24,17 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
24
24
  # @param helper_popover_placement [String] Placement of the popover (default: "right")
25
25
  # @param name [String] Name of the field (required, unless using form builder)
26
26
  # @param value [String] Value for the field (optional)
27
+ # @param placeholder [String] Placeholder text for the input field (optional, defaults to I18n if object/attribute present, pass false to disable)
27
28
  # @param type [Symbol] Input type (`:text`, `:email`, etc)
28
29
  # @param icon [Symbol] Left icon (optional)
29
30
  # @param right_icon [Symbol] Right icon (optional)
30
31
  # @param addon [String] Add-on text or icon before the input (optional)
31
- # @param addon_props [Hash] Props for the Add-on (optional)
32
- # @param icon_props [Hash] Props for the left icon (optional)
33
- # @param right_icon_props [Hash] Props for the right icon (optional)
34
- # @param div_props [Hash] Props for the whole div (optional)
32
+ # @param addon_html [Hash] Props for the Add-on (optional)
33
+ # @param icon_html [Hash] Props for the left icon (optional)
34
+ # @param right_icon_html [Hash] Props for the right icon (optional)
35
+ # @param div_html [Hash] Props for the whole div (optional)
35
36
  # @param multiline [Boolean] Renders a textarea if true
36
- # @param color [Symbol] Field color (`:default`, `:success`, `:failure`, etc)
37
+ # @param color [Symbol] Field color (`:default`, `:success`, `:danger`, etc)
37
38
  # @param sizing [Integer] Input size
38
39
  # @param shadow [Boolean] Adds drop shadow if true
39
40
  # @param disabled [Boolean] Disables the input if true
@@ -48,13 +49,14 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
48
49
  @shadow = @props.delete(:shadow)
49
50
  @addon = @props.delete(:addon)
50
51
  @right_icon = @props.delete(:right_icon)
51
- @addon_props = @props.delete(:addon_props) || {}
52
- @div_props = @props.delete(:div_props) || {}
53
- @icon_props = @props.delete(:icon_props) || {}
54
- @right_icon_props = @props.delete(:right_icon_props) || {}
52
+ @addon_html = @props.delete(:addon_html) || {}
53
+ @div_html = @props.delete(:div_html) || {}
54
+ @icon_html = @props.delete(:icon_html) || {}
55
+ @right_icon_html = @props.delete(:right_icon_html) || {}
55
56
  @sizing = sizing_with_addon @props.delete(:sizing)
56
57
  @props[:type] = @type
57
58
 
59
+ define_placeholder(@props.delete(:placeholder))
58
60
  declare_classes
59
61
  @props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
60
62
  end
@@ -67,9 +69,23 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
67
69
 
68
70
  private
69
71
 
72
+ def define_placeholder(placeholder)
73
+ return if placeholder.is_a?(FalseClass)
74
+
75
+ if placeholder.nil? && @object.present? && @attribute.present?
76
+ placeholder = I18n.t(
77
+ @attribute,
78
+ scope: [ @object.class.name.pluralize.underscore.to_sym, :placeholders ],
79
+ default: nil
80
+ )
81
+ end
82
+
83
+ @props[:placeholder] = placeholder if placeholder.present?
84
+ end
85
+
70
86
  def valid_color(color)
71
87
  return color if styles[:bg].key?(color)
72
- return :failure if errors.present?
88
+ return :danger if errors.present?
73
89
 
74
90
  @@color
75
91
  end
@@ -101,7 +117,7 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
101
117
  content_tag(
102
118
  tag,
103
119
  anyicon(
104
- icon: icon_v,
120
+ icon_v,
105
121
  class: styles[:additional_icons][:class][@color]
106
122
  ),
107
123
  **props
@@ -109,20 +125,20 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
109
125
  end
110
126
 
111
127
  def create_icon
112
- add class: styles[:additional_icons][:icon], to: @icon_props
113
- add(class: "pointer-events-none", to: @icon_props) unless events?(@icon_props)
114
- icon(@icon, props: @icon_props)
128
+ add class: styles[:additional_icons][:icon], to: @icon_html
129
+ add(class: "pointer-events-none", to: @icon_html) unless events?(@icon_html)
130
+ icon(@icon, props: @icon_html)
115
131
  end
116
132
 
117
133
  def create_addon
118
- add class: styles[:additional_icons][:addon][@color], to: @addon_props
119
- icon(@addon, tag: :span, props: @addon_props)
134
+ add class: styles[:additional_icons][:addon][@color], to: @addon_html
135
+ icon(@addon, tag: :span, props: @addon_html)
120
136
  end
121
137
 
122
138
  def create_right_icon
123
- add class: styles[:additional_icons][:right_icon], to: @right_icon_props
124
- add(class: "pointer-events-none", to: @right_icon_props) unless events?(@right_icon_props)
125
- icon(@right_icon, props: @right_icon_props)
139
+ add class: styles[:additional_icons][:right_icon], to: @right_icon_html
140
+ add(class: "pointer-events-none", to: @right_icon_html) unless events?(@right_icon_html)
141
+ icon(@right_icon, props: @right_icon_html)
126
142
  end
127
143
 
128
144
  def events?(props)
@@ -143,6 +159,7 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
143
159
  end
144
160
 
145
161
  if @form.present? && @attribute.present?
162
+ @props[:value] = @value if @value.present?
146
163
  @form.public_send(input_type, @attribute, @props)
147
164
  else
148
165
  public_send("#{input_type}_tag", @name, @value, @props)
@@ -150,13 +167,13 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
150
167
  end
151
168
 
152
169
  def icon_container_with_addon
153
- add class: "flex", to: @div_props
154
- content_tag :div, safe_join([ create_addon, create_right_icon, input ]), @div_props
170
+ add class: "flex", to: @div_html
171
+ content_tag :div, safe_join([ create_addon, create_right_icon, input ]), @div_html
155
172
  end
156
173
 
157
174
  def icon_container_without_addon
158
- add class: "relative w-full", to: @div_props
159
- content_tag :div, safe_join([ create_icon, create_right_icon, input ]), @div_props
175
+ add class: "relative w-full", to: @div_html
176
+ content_tag :div, safe_join([ create_icon, create_right_icon, input ]), @div_html
160
177
  end
161
178
 
162
179
  def icon_container
@@ -27,7 +27,7 @@ class Fluxbit::Form::ToggleComponent < Fluxbit::Form::FieldComponent
27
27
  # @param name [String] Name of the field (required unless using form builder)
28
28
  # @param other_label [String] Additional label, rendered via slot (optional)
29
29
  # @param sizing [Integer] Size index for the toggle (default: config)
30
- # @param color [Symbol] Checked toggle color (:default, :success, :failure, :info, :warning, etc)
30
+ # @param color [Symbol] Checked toggle color (:default, :success, :danger, :info, :warning, etc)
31
31
  # @param unchecked_color [Symbol] Unchecked toggle color (see config)
32
32
  # @param button_color [Symbol] Color for the toggle button
33
33
  # @param invert_label [Boolean] If true, inverts label/toggle order (default: config)
@@ -54,7 +54,7 @@ class Fluxbit::Form::ToggleComponent < Fluxbit::Form::FieldComponent
54
54
 
55
55
  def valid_color(color)
56
56
  return color if styles[:toggle][:checked].key?(color)
57
- return :failure if errors.present?
57
+ return :danger if errors.present?
58
58
 
59
59
  @@color
60
60
  end
@@ -3,7 +3,7 @@
3
3
  <%= label %>
4
4
  <div class="mt-1 lg:hidden">
5
5
  <div class="flex items-center">
6
- <div class="inline-block h-12 w-12 shrink-0 overflow-hidden rounded-full relative" aria-hidden="true">
6
+ <div class="inline-block h-12 w-12 shrink-0 overflow-hidden <%= container_rounded_class %> relative" aria-hidden="true">
7
7
  <%= image_element %>
8
8
  </div>
9
9
  <div class="ml-5 rounded-md shadow-xs">
@@ -18,8 +18,8 @@
18
18
  </div>
19
19
  </div>
20
20
 
21
- <div class="relative hidden overflow-hidden rounded-full lg:block w-40">
22
- <div class="inline-block h-40 w-40 shrink-0 overflow-hidden rounded-full relative" aria-hidden="true">
21
+ <div class="relative hidden overflow-hidden <%= container_rounded_class %> lg:block w-40">
22
+ <div class="inline-block h-40 w-40 shrink-0 overflow-hidden <%= container_rounded_class %> relative" aria-hidden="true">
23
23
  <%= image_element %>
24
24
  </div>
25
25
  <label for="desktop-<%= id %>" class="absolute inset-0 flex flex-col h-full w-full items-center justify-center bg-blue-800/75 text-sm font-medium text-white opacity-0 hover:opacity-100">
@@ -22,11 +22,14 @@ class Fluxbit::Form::UploadImageComponent < Fluxbit::Form::FieldComponent
22
22
  # @param image_path [String] Path to the image to be displayed (optional)
23
23
  # @param image_placeholder [String] Placeholder image path if no image is attached (optional)
24
24
  # @param title [Boolean, String] Whether to show a title (true for default, false to hide, or custom string)
25
+ # @param rounded [Boolean] Whether to show image as circle (true, default) or square with rounded edges (false)
25
26
  # @param class [String] Additional CSS classes for the input element
26
27
  # @param ... any other HTML attribute supported by file_field_tag
27
28
  def initialize(**props)
28
29
  super(**props)
29
30
  @title = @props.delete(:title) || "Change"
31
+ @rounded = @props.delete(:rounded)
32
+ @rounded = true if @rounded.nil?
30
33
  @image_path = @props.delete(:image_path) ||
31
34
  (if @object&.send(@attribute).respond_to?(:attached?) && @object&.send(@attribute)&.send("attached?")
32
35
  @object&.send(@attribute)&.variant(resize_to_fit: [ 160, 160 ])
@@ -44,7 +47,15 @@ class Fluxbit::Form::UploadImageComponent < Fluxbit::Form::FieldComponent
44
47
 
45
48
  def image_element
46
49
  image_tag @image_path,
47
- class: "img_photo_#{id} img_photo absolute inset-0 w-full h-full object-cover rounded-full",
50
+ class: "img_photo_#{id} img_photo absolute inset-0 w-full h-full object-cover #{image_rounded_class}",
48
51
  alt: @attribute&.to_s&.humanize
49
52
  end
53
+
54
+ def container_rounded_class
55
+ @rounded ? "rounded-full" : "rounded-lg"
56
+ end
57
+
58
+ def image_rounded_class
59
+ @rounded ? "rounded-full" : "rounded-lg"
60
+ end
50
61
  end
@@ -24,6 +24,7 @@ class Fluxbit::GravatarComponent < Fluxbit::AvatarComponent
24
24
  # @option props [Symbol] :filetype (:png) The filetype of the Gravatar (:png, :jpg, :gif).
25
25
  # @option props [Symbol] :default (:identicon) The default image to use if no Gravatar is found.
26
26
  # @option props [Integer] :size (:md) The size of the Gravatar base on the size provided by AvatarComponent.
27
+ # @option props [Boolean] :url_only (false) If true, returns only the Gravatar URL instead of rendering the avatar component.
27
28
  # @option props [String] :remove_class ('') Classes to be removed from the default Gravatar class list.
28
29
  # @option props [Hash] **props Remaining options declared as HTML attributes, applied to the Gravatar container.
29
30
  def initialize(**props)
@@ -37,10 +38,16 @@ class Fluxbit::GravatarComponent < Fluxbit::AvatarComponent
37
38
  }
38
39
  add class: gravatar_styles[:base], to: @props
39
40
  @email = @props.delete(:email)
41
+ @url_only = @props.delete(:url_only)
40
42
  src = gravatar_url
41
43
  super(src: src, **@props)
42
44
  end
43
45
 
46
+ def call
47
+ return gravatar_url.html_safe if @url_only
48
+ super
49
+ end
50
+
44
51
  # The raw MD5 hash of the users' email. Gravatar is particularly tricky as
45
52
  # it downcases all emails. This is really the guts of the module,
46
53
  # everything else is just convenience.
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit
4
+ module IconHelpers
5
+ def chevron_right(**props)
6
+ add to: props, class: "w-2.5 h-2.5", first_element: true
7
+ remove_class_from_props(props)
8
+ props["aria-hidden"] = "true"
9
+ props[:xmlns] = "http://www.w3.org/2000/svg"
10
+ props[:fill] = "none"
11
+ props[:viewBox] = "0 0 6 10"
12
+ stroke_width = props.delete(:stroke_width) || 2
13
+
14
+ tag.svg(**props) do
15
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "m1 9 4-4-4-4")
16
+ end
17
+ end
18
+
19
+ def chevron_left(**props)
20
+ add to: props, class: "w-2.5 h-2.5", first_element: true
21
+ remove_class_from_props(props)
22
+ props["aria-hidden"] = "true"
23
+ props[:xmlns] = "http://www.w3.org/2000/svg"
24
+ props[:fill] = "none"
25
+ props[:viewBox] = "0 0 6 10"
26
+ stroke_width = props.delete(:stroke_width) || 2
27
+
28
+ tag.svg(**props) do
29
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "m5 1-4 4 4 4")
30
+ end
31
+ end
32
+
33
+ def chevron_up(**props)
34
+ add to: props, class: "w-2.5 h-2.5", first_element: true
35
+ remove_class_from_props(props)
36
+ props["aria-hidden"] = "true"
37
+ props[:xmlns] = "http://www.w3.org/2000/svg"
38
+ props[:fill] = "none"
39
+ props[:viewBox] = "0 0 10 6"
40
+ stroke_width = props.delete(:stroke_width) || 2
41
+
42
+ tag.svg(**props) do
43
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "m1 5 4-4 4 4")
44
+ end
45
+ end
46
+
47
+ def chevron_down(**props)
48
+ add to: props, class: "w-2.5 h-2.5", first_element: true
49
+ remove_class_from_props(props)
50
+ props["aria-hidden"] = "true"
51
+ props[:xmlns] = "http://www.w3.org/2000/svg"
52
+ props[:fill] = "none"
53
+ props[:viewBox] = "0 0 10 6"
54
+ stroke_width = props.delete(:stroke_width) || 2
55
+
56
+ tag.svg(**props) do
57
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "m1 1 4 4 4-4")
58
+ end
59
+ end
60
+
61
+ def close_icon(**props)
62
+ props["aria-hidden"] = "true"
63
+ props[:xmlns] = "http://www.w3.org/2000/svg"
64
+ props[:fill] = "none"
65
+ props[:viewBox] = "0 0 14 14"
66
+
67
+ tag.svg(**props) do
68
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => 2, d: "m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6")
69
+ end
70
+ end
71
+
72
+ def plus_icon(**props)
73
+ props["aria-hidden"] = "true"
74
+ props[:xmlns] = "http://www.w3.org/2000/svg"
75
+ props[:fill] = "currentColor"
76
+ props[:viewBox] = "0 0 24 24"
77
+
78
+ tag.svg(**props) do
79
+ tag.path("fill-rule" => "evenodd", d: "M12 3.75a.75.75 0 0 1 .75.75v6.75h6.75a.75.75 0 0 1 0 1.5h-6.75v6.75a.75.75 0 0 1-1.5 0v-6.75H4.5a.75.75 0 0 1 0-1.5h6.75V4.5a.75.75 0 0 1 .75-.75Z", "clip-rule" => "evenodd")
80
+ end
81
+ end
82
+
83
+ def chevron_double_left(**props)
84
+ add to: props, class: "w-3 h-2.5", first_element: true
85
+ remove_class_from_props(props)
86
+ props["aria-hidden"] = "true"
87
+ props[:xmlns] = "http://www.w3.org/2000/svg"
88
+ props[:fill] = "none"
89
+ props[:viewBox] = "0 0 10 10"
90
+ stroke_width = props.delete(:stroke_width) || 2
91
+
92
+ tag.svg(**props) do
93
+ safe_join [
94
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "M4 1l-4 4 4 4"),
95
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "M10 1l-4 4 4 4")
96
+ ]
97
+ end
98
+ end
99
+
100
+ def chevron_double_right(**props)
101
+ add to: props, class: "w-3 h-2.5", first_element: true
102
+ remove_class_from_props(props)
103
+ props["aria-hidden"] = "true"
104
+ props[:xmlns] = "http://www.w3.org/2000/svg"
105
+ props[:fill] = "none"
106
+ props[:viewBox] = "0 0 12 10"
107
+ stroke_width = props.delete(:stroke_width) || 2
108
+
109
+ tag.svg(**props) do
110
+ safe_join [
111
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "M2 1l4 4-4 4"),
112
+ tag.path(stroke: "currentColor", "stroke-linecap" => "round", "stroke-linejoin" => "round", "stroke-width" => stroke_width, d: "M8 1l4 4-4 4")
113
+ ]
114
+ end
115
+ end
116
+
117
+ def ellipsis_horizontal(**props)
118
+ add to: props, class: "w-2.5 h-2.5", first_element: true
119
+ remove_class_from_props(props)
120
+ props["aria-hidden"] = "true"
121
+ props[:xmlns] = "http://www.w3.org/2000/svg"
122
+ props[:fill] = "currentColor"
123
+ props[:viewBox] = "0 0 10 2"
124
+
125
+ tag.svg(**props) do
126
+ safe_join [
127
+ tag.circle(cx: 1, cy: 1, r: 1),
128
+ tag.circle(cx: 5, cy: 1, r: 1),
129
+ tag.circle(cx: 9, cy: 1, r: 1)
130
+ ]
131
+ end
132
+ end
133
+
134
+ def eye_icon(**props)
135
+ add to: props, class: "size-4", first_element: true
136
+ remove_class_from_props(props)
137
+ props["aria-hidden"] = "true"
138
+ props[:xmlns] = "http://www.w3.org/2000/svg"
139
+ props[:fill] = "none"
140
+ props[:viewBox] = "0 0 24 24"
141
+ props[:"stroke-width"] = "1.5"
142
+ props[:stroke] = "currentColor"
143
+
144
+ tag.svg(**props) do
145
+ safe_join [
146
+ tag.path("stroke-linecap" => "round", "stroke-linejoin" => "round", d: "M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"),
147
+ tag.path("stroke-linecap" => "round", "stroke-linejoin" => "round", d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z")
148
+ ]
149
+ end
150
+ end
151
+
152
+ def eye_slash_icon(**props)
153
+ add to: props, class: "size-4", first_element: true
154
+ remove_class_from_props(props)
155
+ props["aria-hidden"] = "true"
156
+ props[:xmlns] = "http://www.w3.org/2000/svg"
157
+ props[:fill] = "none"
158
+ props[:viewBox] = "0 0 24 24"
159
+ props[:"stroke-width"] = "1.5"
160
+ props[:stroke] = "currentColor"
161
+
162
+ tag.svg(**props) do
163
+ tag.path("stroke-linecap" => "round", "stroke-linejoin" => "round", d: "M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88")
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ ##
5
+ # The `Fluxbit::LinkComponent` is a customizable link component that extends {Fluxbit::Component}.
6
+ # It provides a straightforward way to generate anchor elements (`<a>`) with configurable colors
7
+ # and styling options. Additional HTML attributes can be passed to further control the component's
8
+ # behavior and appearance.
9
+ #
10
+ # Example usage:
11
+ # = render Fluxbit::LinkComponent.new(size: 2, spacing: :wider, line_height: :relaxed) do
12
+ # "My Heading"
13
+ #
14
+ class Fluxbit::LinkComponent < Fluxbit::Component
15
+ include Fluxbit::Config::LinkComponent
16
+
17
+ ##
18
+ # Initializes the link component with the provided options.
19
+ #
20
+ # @param [Symbol] color (:default) The color style for the heading. Must be one of the keys defined in +styles[:colors]+.
21
+ # @param [Hash] props Additional HTML attributes to be applied to the heading element, such as +class+, +id+, +data-*, etc.
22
+ #
23
+ # @return [Fluxbit::LinkComponent]
24
+ #
25
+ # @example
26
+ # = render Fluxbit::LinkComponent.new(color: :primary) do
27
+ # "My Heading"
28
+ #
29
+ def initialize(**props)
30
+ super
31
+ @props = props
32
+ @color = @props.delete(:color) || @@color
33
+
34
+ add to: @props, class: [ styles[:colors][@color], styles[:base] ]
35
+ @props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
36
+ @href = @props.delete(:href) || "#"
37
+ end
38
+
39
+ def call
40
+ link_to content, @href, **@props
41
+ end
42
+ end
@@ -23,10 +23,10 @@ class Fluxbit::ModalComponent < Fluxbit::Component
23
23
  # @option props [Boolean] :only_css (false) Determines if the modal can be closed by clicking the backdrop, using a CSS-based approach.
24
24
  # @option props [Boolean] :static (false) If true, the modal will not close when clicking the backdrop or pressing the ESC key.
25
25
  # @option props [String] :remove_class ('') Classes to be removed from the default modal class list.
26
- # @option props [Hash] :content_props ({}) Additional HTML attributes and classes for the content wrapper inside the modal.
27
- # @option props [Hash] :header_props ({}) Additional HTML attributes and classes for the header section.
28
- # @option props [Hash] :footer_props ({}) Additional HTML attributes and classes for the footer section.
29
- # @option props [Hash] :close_button_props ({}) Additional HTML attributes and classes for the close button element.
26
+ # @option props [Hash] :content_html ({}) Additional HTML attributes and classes for the content wrapper inside the modal.
27
+ # @option props [Hash] :header_html ({}) Additional HTML attributes and classes for the header section.
28
+ # @option props [Hash] :footer_html ({}) Additional HTML attributes and classes for the footer section.
29
+ # @option props [Hash] :close_button_html ({}) Additional HTML attributes and classes for the close button element.
30
30
  # @option props [Hash] **props Remaining options declared as HTML attributes, applied to the modal container.
31
31
  def initialize(**props)
32
32
  super
@@ -50,39 +50,35 @@ class Fluxbit::ModalComponent < Fluxbit::Component
50
50
  @props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
51
51
 
52
52
  # Content properties
53
- @content_props = @props.delete(:content_props) || {}
54
- add(class: content_classes, to: @content_props, first_element: true)
55
- @content_props[:class] = remove_class(@content_props.delete(:remove_class) || "", @content_props[:class])
53
+ @content_html = @props.delete(:content_html) || {}
54
+ add(class: content_classes, to: @content_html, first_element: true)
55
+ @content_html[:class] = remove_class(@content_html.delete(:remove_class) || "", @content_html[:class])
56
56
 
57
57
  # Header properties
58
- @header_props = @props.delete(:header_props) || {}
59
- add(class: header_classes, to: @header_props, first_element: true)
60
- @header_props[:class] = remove_class(@header_props.delete(:remove_class) || "", @header_props[:class])
58
+ @header_html = @props.delete(:header_html) || {}
59
+ add(class: header_classes, to: @header_html, first_element: true)
60
+ @header_html[:class] = remove_class(@header_html.delete(:remove_class) || "", @header_html[:class])
61
61
 
62
62
  # Footer properties
63
- @footer_props = @props.delete(:footer_props) || {}
64
- add(class: footer_classes, to: @footer_props, first_element: true)
65
- @footer_props[:class] = remove_class(@footer_props.delete(:remove_class) || "", @footer_props[:class])
63
+ @footer_html = @props.delete(:footer_html) || {}
64
+ add(class: footer_classes, to: @footer_html, first_element: true)
65
+ @footer_html[:class] = remove_class(@footer_html.delete(:remove_class) || "", @footer_html[:class])
66
66
 
67
67
  # Close button properties
68
- @close_button_props = @props.delete(:close_button_props) || {}
69
- add(class: styles[:header][:close][:base], to: @close_button_props, first_element: true)
70
- @close_button_props[:class] = remove_class(@close_button_props.delete(:remove_class) || "", @close_button_props[:class])
71
- @close_button_props[:type] = "button"
72
- @close_button_props["data-modal-hide"] = @props[:id]
73
- @close_button_props["aria-label"] = "Close"
68
+ @close_button_html = @props.delete(:close_button_html) || {}
69
+ add(class: styles[:header][:close][:base], to: @close_button_html, first_element: true)
70
+ @close_button_html[:class] = remove_class(@close_button_html.delete(:remove_class) || "", @close_button_html[:class])
71
+ @close_button_html[:type] = "button"
72
+ @close_button_html["data-modal-hide"] = @props[:id]
74
73
  end
75
74
 
76
75
  def call
77
- content_tag(
78
- :div,
79
- **@props
80
- ) do
81
- content_tag(:div, **@content_props) do
82
- content_tag(:div, class: styles[:content][:inner]) do
76
+ tag.div(**@props) do
77
+ tag.div(**@content_html) do
78
+ tag.div(class: styles[:content][:inner]) do
83
79
  concat(header) if title? || @title.present? || @close_button
84
- concat(content_tag(:div, content, class: body_classes))
85
- concat(content_tag(:div, footer, **@footer_props)) if footer?
80
+ concat(tag.div(content, class: body_classes))
81
+ concat(tag.div(footer, **@footer_html)) if footer?
86
82
  end
87
83
  end
88
84
  end
@@ -114,7 +110,7 @@ class Fluxbit::ModalComponent < Fluxbit::Component
114
110
  def header
115
111
  return close_button if @close_button && !title? && !@title.present?
116
112
 
117
- content_tag(:div, **@header_props) do
113
+ content_tag(:div, **@header_html) do
118
114
  concat(title) if title?
119
115
  concat(content_tag(:h3, @title, class: styles[:header][:title])) if @title.present?
120
116
  concat(close_button) if @close_button
@@ -126,12 +122,13 @@ class Fluxbit::ModalComponent < Fluxbit::Component
126
122
  end
127
123
 
128
124
  def close_button
125
+ @close_button_html["aria-label"] = t("fluxbit.modal.aria_close")
129
126
  content_tag(
130
127
  :button,
131
- **@close_button_props
128
+ **@close_button_html
132
129
  ) do
133
- concat content_tag(:span, "Dismiss", class: "sr-only")
134
- concat anyicon(icon: "heroicons_outline:x-mark", class: "w-5 h-5")
130
+ concat content_tag(:span, t("fluxbit.modal.dismiss"), class: "sr-only")
131
+ concat close_icon(class: "size-3")
135
132
  end
136
133
  end
137
134