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,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,20 +3,59 @@
3
3
  module Fluxbit
4
4
  module ComponentsHelper
5
5
  # Components
6
- [ :avatar, :avatar_group, :gravatar, :alert, :button, :button_group,
7
- :badge, :card, :modal, :popover, :tooltip, :flex, :tab ].each do |component|
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
8
  define_method("fx_#{component}") do |*args, **kwargs, &block|
9
9
  fluxbit_method(component.to_s.camelize, *args, **kwargs, &block)
10
10
  end
11
11
  end
12
12
 
13
13
  # Forms
14
- [ :help_text, :check_box, :form_builder, :label, :range,
15
- :select, :toggle, :upload_image, :dropzone ].each do |component|
14
+ [ :help_text, :form_builder, :label, :range,
15
+ :toggle, :upload_image, :dropzone, :radio_group_button ].each do |component|
16
16
  define_method("fx_#{component}") do |*args, **kwargs, &block|
17
17
  fluxbit_method("Form::#{component.to_s.camelize}", *args, **kwargs, &block)
18
18
  end
19
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
20
59
  def form_builder(...) = fluxbit_method("Form::FormBuilder", ...)
21
60
 
22
61
  Fluxbit::Form::TextFieldComponent::TYPE_OPTIONS.each do |type|
@@ -30,10 +69,41 @@ module Fluxbit
30
69
  fluxbit_method("Form::CheckBox", *args, type: type, **kwargs, &block)
31
70
  end
32
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]
@@ -22,14 +22,17 @@ module Fluxbit
22
22
  within: within,
23
23
  data: { errors_summary: true }
24
24
  ) do |banner|
25
- [
26
- render(Fluxbit::ListComponent.new) do |list|
27
- object.errors.full_messages.each do |error|
28
- list.with_item { error.html_safe }
29
- end
30
- end,
31
- (template.capture { yield(banner) } if block_given?)
32
- ].compact.join.html_safe
25
+ parts = []
26
+
27
+ parts << render(Fluxbit::ListComponent.new) do |list|
28
+ object.errors.full_messages.each do |error|
29
+ list.with_item { template.sanitize(error.to_s, tags: [], attributes: []) }
30
+ end
31
+ end
32
+
33
+ parts << template.capture { yield(banner) } if block_given?
34
+
35
+ template.safe_join(parts)
33
36
  end
34
37
  end
35
38
 
@@ -65,7 +68,7 @@ module Fluxbit
65
68
  end
66
69
  end
67
70
 
68
- [ :range, :toggle, :upload_image, :dropzone ].each do |component|
71
+ [ :range, :toggle, :upload_image, :dropzone, :password ].each do |component|
69
72
  define_method("fx_#{component}") do |method, **options, &block|
70
73
  options[:error] ||= error_for(method)
71
74
  options[:error] = !!options[:error] if options[:error_hidden] && options[:error]
@@ -74,14 +77,60 @@ module Fluxbit
74
77
  end
75
78
  end
76
79
 
77
- # select(object, method, choices = nil, options = {}, html_options = {}, &block) public
78
- def fx_select(method, **options, &block)
79
- options[:error] ||= error_for(method)
80
- options[:error] = !!options[:error] if options[:error_hidden] && options[:error]
80
+ def fx_radio_group_button(method, **options, &block)
81
+ options[:name] ||= "#{@object_name}[#{method}]"
82
+ render Fluxbit::Form::RadioGroupButtonComponent.new(**options), &block
83
+ end
84
+
85
+ # Mimics Rails' form.select signature:
86
+ # select(method, choices = nil, options = {}, html_options = {}, &block)
87
+ #
88
+ # @param method [Symbol] The attribute name
89
+ # @param choices [Array, Hash, String, nil] Options for the select (raw data or pre-formatted HTML)
90
+ # @param options [Hash] Options like prompt, include_blank, selected, disabled
91
+ # @param html_options [Hash] HTML attributes for the select tag
92
+ # @param block [Proc] Optional block for custom options
93
+ #
94
+ # @example Basic usage
95
+ # form.fx_select :role, ["Admin", "User", "Guest"]
96
+ #
97
+ # @example With options
98
+ # form.fx_select :country, countries, { prompt: "Select a country" }, { class: "custom-class" }
99
+ #
100
+ # @example With pre-formatted options
101
+ # form.fx_select :status, options_for_select(statuses, selected: "active")
102
+ def fx_select(method, choices = nil, options = {}, html_options = {}, &block)
103
+ # Handle Rails-style signature
104
+ all_options = html_options.merge(options)
105
+
106
+ # Set the choices/options
107
+ all_options[:options] = choices if choices.present?
108
+
109
+ # Add error handling
110
+ all_options[:error] ||= error_for(method)
111
+ all_options[:error] = !!all_options[:error] if all_options[:error_hidden] && all_options[:error]
112
+
113
+ # Set selected value from object if not explicitly provided
81
114
  value = object&.public_send(method)
82
- options[:selected] = value if value.present?
115
+ all_options[:selected] ||= value if value.present?
116
+
117
+ render Fluxbit::Form::SelectComponent.new(form: self, attribute: method, **all_options, &block)
118
+ end
119
+
120
+ def fx_submit(content = nil, **options, &block)
121
+ options[:form] = self
122
+ options[:content] = content if content.present?
123
+ options[:color] ||= :primary
124
+ options[:size] ||= 2
125
+ options[:disabled] = true if object&.persisted? && !object.valid?
83
126
 
84
- render Fluxbit::Form::SelectComponent.new(form: self, attribute: method, **options, &block)
127
+ if block_given?
128
+ render Fluxbit::ButtonComponent.new(**options) do |*args|
129
+ yield(*args)
130
+ end
131
+ else
132
+ render Fluxbit::ButtonComponent.new(**options)
133
+ end
85
134
  end
86
135
  end
87
136
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit
4
+ module ViewHelper
5
+ def fx_body_class
6
+ "h-full bg-slate-100 dark:bg-slate-900 dark:text-white"
7
+ end
8
+
9
+ def fx_sort_field(field, url, label = nil)
10
+ order = (request.query_parameters[:order] || "").rpartition("_")
11
+
12
+ order_direction = "asc"
13
+ if order.first == field.to_s
14
+ order_direction = order.last == "asc" ? "desc" : "asc"
15
+ end
16
+
17
+ link_to public_send(url, request.query_parameters.merge(order: "#{field}_#{order_direction}")), class: "order-#{order_direction} flex" do
18
+ label_text = label || field.to_s.titlecase
19
+ icon = if order.first == field.to_s
20
+ order_direction == "asc" ? fx_sort_up : fx_sort_down
21
+ else
22
+ fx_sort_up_down
23
+ end
24
+
25
+ safe_join([ h(label_text), icon ], " ")
26
+ end
27
+ end
28
+
29
+ def fx_sort_up_down
30
+ content_tag :svg, class: "size-4", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor" do
31
+ concat(
32
+ content_tag(
33
+ :path,
34
+ "",
35
+ fill_rule: "evenodd",
36
+ d: "M11.47 4.72a.75.75 0 0 1 1.06 0l3.75 3.75a.75.75 0 0 1-1.06 1.06L12 6.31 8.78 9.53a.75.75 0 0 1-1.06-1.06l3.75-3.75Zm-3.75 9.75a.75.75 0 0 1 1.06 0L12 17.69l3.22-3.22a.75.75 0 1 1 1.06 1.06l-3.75 3.75a.75.75 0 0 1-1.06 0l-3.75-3.75a.75.75 0 0 1 0-1.06Z",
37
+ clip_rule: "evenodd"
38
+ )
39
+ )
40
+ end
41
+ end
42
+
43
+ def fx_sort_up
44
+ content_tag :svg, class: "size-4", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor" do
45
+ concat(
46
+ content_tag(
47
+ :path,
48
+ "",
49
+ fill_rule: "evenodd",
50
+ d: "M11.47 4.72a.75.75 0 0 1 1.06 0l3.75 3.75a.75.75 0 0 1-1.06 1.06L12 6.31 8.78 9.53a.75.75 0 0 1-1.06-1.06l3.75-3.75Z",
51
+ clip_rule: "evenodd"
52
+ )
53
+ )
54
+ end
55
+ end
56
+
57
+ def fx_sort_down
58
+ content_tag :svg, class: "size-4", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor" do
59
+ concat(
60
+ content_tag(
61
+ :path,
62
+ "",
63
+ fill_rule: "evenodd",
64
+ d: "M11.47 19.28a.75.75 0 0 1 1.06 0l3.75-3.75a.75.75 0 0 1-1.06-1.06L12 17.69l-3.22-3.22a.75.75 0 1 1-1.06 1.06l3.75 3.75Z",
65
+ clip_rule: "evenodd"
66
+ )
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,6 +1,39 @@
1
1
  en:
2
2
  fluxbit:
3
- form_builder:
4
- errors_summary:
5
- one: "There is 1 error with this %{model}:"
6
- other: "There are %{count} errors with this %{model}:"
3
+ alert:
4
+ aria_close: Close
5
+ dismiss: Dismiss
6
+ banner:
7
+ aria_close: Close
8
+ dismiss: Dismiss
9
+ drawer:
10
+ dismiss: Dismiss
11
+ modal:
12
+ aria_close: Close
13
+ dismiss: Dismiss
14
+ pagination:
15
+ aria_label:
16
+ nav:
17
+ one: Page
18
+ other: Pages
19
+ prev: Previous
20
+ next: Next
21
+ first: First
22
+ last: Last
23
+ first: First
24
+ last: Last
25
+ prev: Previous
26
+ next: Next
27
+ skeleton:
28
+ loading: Loading...
29
+ speed_dial:
30
+ open_actions_menu: Open actions menu
31
+ form:
32
+ password:
33
+ checks:
34
+ length: At least %{count} characters
35
+ uppercase: Contains uppercase letter
36
+ lowercase: Contains lowercase letter
37
+ numbers: Contains number
38
+ special: Contains special character
39
+ strength: Password strength
@@ -0,0 +1,36 @@
1
+ pt-BR:
2
+ fluxbit:
3
+ alert:
4
+ aria_close: Close
5
+ dismiss: Dismiss
6
+ drawer:
7
+ dismiss: Dismiss
8
+ modal:
9
+ aria_close: Close
10
+ dismiss: Dismiss
11
+ pagination:
12
+ aria_label:
13
+ nav:
14
+ one: Página
15
+ other: Páginas
16
+ prev: Anterior
17
+ next: Próximo
18
+ first: Primeiro
19
+ last: Último
20
+ first: Primeiro
21
+ last: Último
22
+ prev: Anterior
23
+ next: Próximo
24
+ skeleton:
25
+ loading: Carregando...
26
+ speed_dial:
27
+ open_actions_menu: Abrir menu de ações
28
+ form:
29
+ password:
30
+ checks:
31
+ length: Pelo menos %{count} caracteres
32
+ uppercase: Contém letra maiúscula
33
+ lowercase: Contém letra minúscula
34
+ numbers: Contém número
35
+ special: Contém caractere especial
36
+ strength: Força da senha
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fluxbit::Config::AccordionComponent
4
+ mattr_accessor :flush, default: false
5
+ mattr_accessor :color, default: :default
6
+ mattr_accessor :collapse_all, default: false
7
+
8
+ # rubocop: disable Layout/LineLength, Metrics/BlockLength
9
+ mattr_accessor :styles do
10
+ {
11
+ base: "space-y-2",
12
+ item: {
13
+ base: "",
14
+ header: {
15
+ base: "flex items-center justify-between w-full p-5 font-medium rtl:text-right border border-gray-200 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3",
16
+ first: "rounded-t-xl",
17
+ last: "rounded-b-xl",
18
+ middle: "border-b-0"
19
+ },
20
+ content: {
21
+ base: "p-5 border border-gray-200 dark:border-gray-700",
22
+ first: "",
23
+ last: "rounded-b-xl border-t-0",
24
+ middle: "border-t-0 border-b-0"
25
+ },
26
+ icon: {
27
+ base: "w-3 h-3 rotate-180 shrink-0",
28
+ open: "rotate-180",
29
+ closed: ""
30
+ }
31
+ },
32
+ colors: {
33
+ default: {
34
+ header: "bg-white dark:bg-gray-900 text-gray-500 dark:text-gray-400",
35
+ content: "bg-white dark:bg-gray-900 text-gray-500 dark:text-gray-400"
36
+ },
37
+ light: {
38
+ header: "bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white",
39
+ content: "bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white"
40
+ },
41
+ primary: {
42
+ header: "bg-blue-50 dark:bg-blue-900 text-blue-900 dark:text-blue-100 border-blue-200 dark:border-blue-800",
43
+ content: "bg-blue-50 dark:bg-blue-900 text-blue-900 dark:text-blue-100 border-blue-200 dark:border-blue-800"
44
+ },
45
+ secondary: {
46
+ header: "bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 border-gray-300 dark:border-gray-700",
47
+ content: "bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 border-gray-300 dark:border-gray-700"
48
+ },
49
+ success: {
50
+ header: "bg-green-50 dark:bg-green-900 text-green-900 dark:text-green-100 border-green-200 dark:border-green-800",
51
+ content: "bg-green-50 dark:bg-green-900 text-green-900 dark:text-green-100 border-green-200 dark:border-green-800"
52
+ },
53
+ danger: {
54
+ header: "bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100 border-red-200 dark:border-red-800",
55
+ content: "bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100 border-red-200 dark:border-red-800"
56
+ },
57
+ warning: {
58
+ header: "bg-yellow-50 dark:bg-yellow-900 text-yellow-900 dark:text-yellow-100 border-yellow-200 dark:border-yellow-800",
59
+ content: "bg-yellow-50 dark:bg-yellow-900 text-yellow-900 dark:text-yellow-100 border-yellow-200 dark:border-yellow-800"
60
+ },
61
+ info: {
62
+ header: "bg-cyan-50 dark:bg-cyan-900 text-cyan-900 dark:text-cyan-100 border-cyan-200 dark:border-cyan-800",
63
+ content: "bg-cyan-50 dark:bg-cyan-900 text-cyan-900 dark:text-cyan-100 border-cyan-200 dark:border-cyan-800"
64
+ },
65
+ dark: {
66
+ header: "bg-gray-800 dark:bg-gray-700 text-gray-100 dark:text-gray-200 border-gray-700 dark:border-gray-600",
67
+ content: "bg-gray-800 dark:bg-gray-700 text-gray-100 dark:text-gray-200 border-gray-700 dark:border-gray-600"
68
+ }
69
+ }
70
+ }
71
+ end
72
+ # rubocop: enable Layout/LineLength, Metrics/BlockLength
73
+ end
@@ -32,7 +32,7 @@ module Fluxbit::Config::AvatarComponent
32
32
  },
33
33
  color: {
34
34
  dark: "ring-gray-800 dark:ring-gray-800",
35
- failure: "ring-red-500 dark:ring-red-700",
35
+ danger: "ring-red-500 dark:ring-red-700",
36
36
  gray: "ring-gray-500 dark:ring-gray-400",
37
37
  info: "ring-cyan-400 dark:ring-cyan-800",
38
38
  light: "ring-gray-300 dark:ring-gray-500",
@@ -42,20 +42,20 @@ module Fluxbit::Config::AvatarComponent
42
42
  pink: "ring-pink-500 dark:ring-pink-500"
43
43
  },
44
44
  size: {
45
- xs: "w-6 h-6",
46
- sm: "w-8 h-8",
47
- md: "w-10 h-10",
48
- lg: "w-20 h-20",
49
- xl: "w-36 h-36"
45
+ xs: "size-6",
46
+ sm: "size-8",
47
+ md: "size-10",
48
+ lg: "size-20",
49
+ xl: "size-36"
50
50
  },
51
51
  placeholder_icon: {
52
52
  base: "relative overflow-hidden bg-gray-200 dark:bg-gray-600",
53
53
  size: {
54
- xs: "w-8 h-8",
55
- sm: "w-10 h-10",
56
- md: "w-12 h-12",
57
- lg: "w-22 h-22",
58
- xl: "w-38 h-38"
54
+ xs: "size-8",
55
+ sm: "size-10",
56
+ md: "size-12",
57
+ lg: "size-22",
58
+ xl: "size-38"
59
59
  }
60
60
  },
61
61
  stacked: "ring-2 ring-gray-300 dark:ring-gray-500",