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.
- checksums.yaml +4 -4
- data/README.md +10 -0
- data/app/assets/javascripts/fluxbit_view_components/assigner_controller.js +49 -0
- data/app/assets/javascripts/fluxbit_view_components/auto_submit_controller.js +39 -0
- data/app/assets/javascripts/fluxbit_view_components/drawer_controller.js +135 -0
- data/app/assets/javascripts/fluxbit_view_components/index.js +56 -0
- data/app/assets/javascripts/fluxbit_view_components/method_link_controller.js +143 -0
- data/app/assets/javascripts/fluxbit_view_components/modal_controller.js +118 -0
- data/app/assets/javascripts/fluxbit_view_components/password_controller.js +170 -0
- data/app/assets/javascripts/fluxbit_view_components/progress_controller.js +374 -0
- data/app/assets/javascripts/fluxbit_view_components/row_click_controller.js +32 -0
- data/app/assets/javascripts/fluxbit_view_components/select_all_controller.js +122 -0
- data/app/assets/javascripts/fluxbit_view_components/spinner_percent_controller.js +174 -0
- data/app/assets/javascripts/fluxbit_view_components/theme_button_controller.js +90 -0
- data/app/assets/javascripts/fluxbit_view_components.js +1175 -0
- data/app/components/fluxbit/accordion_component.rb +125 -0
- data/app/components/fluxbit/alert_component.rb +8 -8
- data/app/components/fluxbit/avatar_component.rb +11 -12
- data/app/components/fluxbit/avatar_group_component.rb +1 -1
- data/app/components/fluxbit/badge_component.rb +8 -7
- data/app/components/fluxbit/banner_component.rb +139 -0
- data/app/components/fluxbit/bottom_navigation_component.rb +437 -0
- data/app/components/fluxbit/breadcrumb_component.rb +66 -0
- data/app/components/fluxbit/button_component.rb +39 -11
- data/app/components/fluxbit/button_group_component.rb +1 -1
- data/app/components/fluxbit/card_component.rb +26 -23
- data/app/components/fluxbit/carousel_component.rb +154 -0
- data/app/components/fluxbit/component.rb +24 -3
- data/app/components/fluxbit/drawer_component.html.erb +30 -0
- data/app/components/fluxbit/drawer_component.rb +125 -0
- data/app/components/fluxbit/dropdown_component.rb +41 -0
- data/app/components/fluxbit/dropdown_item_component.rb +68 -0
- data/app/components/fluxbit/flex_component.rb +1 -1
- data/app/components/fluxbit/form/check_box_component.rb +56 -0
- data/app/components/fluxbit/form/component.rb +27 -26
- data/app/components/fluxbit/form/dropzone_component.html.erb +39 -0
- data/app/components/fluxbit/form/dropzone_component.rb +39 -0
- data/app/components/fluxbit/form/field_component.rb +28 -0
- data/app/components/fluxbit/form/form_builder_component.rb +1 -1
- data/app/components/fluxbit/form/{helper_text_component.rb → help_text_component.rb} +9 -4
- data/app/components/fluxbit/form/label_component.rb +40 -30
- data/app/components/fluxbit/form/password_component.rb +247 -0
- data/app/components/fluxbit/form/radio_group_button_component.rb +126 -0
- data/app/components/fluxbit/form/range_component.rb +52 -0
- data/app/components/fluxbit/form/select_component.rb +185 -0
- data/app/components/fluxbit/form/text_field_component.rb +185 -0
- data/app/components/fluxbit/form/toggle_component.html.erb +23 -0
- data/app/components/fluxbit/form/toggle_component.rb +81 -0
- data/app/components/fluxbit/form/upload_image_component.html.erb +50 -0
- data/app/components/fluxbit/form/upload_image_component.rb +61 -0
- data/app/components/fluxbit/gravatar_component.rb +7 -0
- data/app/components/fluxbit/icon_helpers.rb +167 -0
- data/app/components/fluxbit/link_component.rb +42 -0
- data/app/components/fluxbit/modal_component.rb +28 -31
- data/app/components/fluxbit/pagination_component.rb +206 -0
- data/app/components/fluxbit/popover_component.rb +14 -14
- data/app/components/fluxbit/progress_component.rb +196 -0
- data/app/components/fluxbit/skeleton_component.rb +237 -0
- data/app/components/fluxbit/speed_dial_action_component.html.erb +30 -0
- data/app/components/fluxbit/speed_dial_action_component.rb +59 -0
- data/app/components/fluxbit/speed_dial_component.html.erb +33 -0
- data/app/components/fluxbit/speed_dial_component.rb +73 -0
- data/app/components/fluxbit/spinner_component.rb +71 -0
- data/app/components/fluxbit/spinner_percent_component.rb +174 -0
- data/app/components/fluxbit/stepper_component.rb +223 -0
- data/app/components/fluxbit/tab_component.rb +44 -25
- data/app/components/fluxbit/table_component.rb +186 -0
- data/app/components/fluxbit/table_group_component.rb +28 -0
- data/app/components/fluxbit/theme_button_component.rb +64 -0
- data/app/components/fluxbit/timeline_component.rb +63 -0
- data/app/components/fluxbit/timeline_item_component.html.erb +64 -0
- data/app/components/fluxbit/timeline_item_component.rb +78 -0
- data/app/components/fluxbit/tooltip_component.rb +2 -2
- data/app/helpers/fluxbit/components_helper.rb +93 -51
- data/app/helpers/fluxbit/form_builder.rb +136 -0
- data/app/helpers/fluxbit/view_helper.rb +71 -0
- data/config/locales/en.yml +37 -4
- data/config/locales/pt-BR.yml +36 -0
- data/lib/fluxbit/config/accordion_component.rb +73 -0
- data/lib/fluxbit/config/avatar_component.rb +11 -11
- data/lib/fluxbit/config/badge_component.rb +14 -11
- data/lib/fluxbit/config/banner_component.rb +60 -0
- data/lib/fluxbit/config/bottom_navigation_component.rb +74 -0
- data/lib/fluxbit/config/breadcrumb_component.rb +24 -0
- data/lib/fluxbit/config/button_component.rb +6 -4
- data/lib/fluxbit/config/card_component.rb +23 -12
- data/lib/fluxbit/config/carousel_component.rb +33 -0
- data/lib/fluxbit/config/drawer_component.rb +48 -0
- data/lib/fluxbit/config/dropdown_component.rb +29 -0
- data/lib/fluxbit/config/form/check_box_component.rb +19 -0
- data/lib/fluxbit/config/form/dropzone_component.rb +20 -0
- data/lib/fluxbit/config/form/{helper_text_component.rb → help_text_component.rb} +2 -2
- data/lib/fluxbit/config/form/label_component.rb +31 -0
- data/lib/fluxbit/config/form/password_component.rb +19 -0
- data/lib/fluxbit/config/form/radio_group_button_component.rb +24 -0
- data/lib/fluxbit/config/form/range_component.rb +15 -0
- data/lib/fluxbit/config/form/text_field_component.rb +76 -0
- data/lib/fluxbit/config/form/toggle_component.rb +79 -0
- data/lib/fluxbit/config/link_component.rb +24 -0
- data/lib/fluxbit/config/modal_component.rb +1 -1
- data/lib/fluxbit/config/pagination_component.rb +31 -0
- data/lib/fluxbit/config/popover_component.rb +1 -1
- data/lib/fluxbit/config/progress_component.rb +63 -0
- data/lib/fluxbit/config/skeleton_component.rb +82 -0
- data/lib/fluxbit/config/speed_dial_component.rb +50 -0
- data/lib/fluxbit/config/spinner_component.rb +30 -0
- data/lib/fluxbit/config/spinner_percent_component.rb +61 -0
- data/lib/fluxbit/config/stepper_component.rb +299 -0
- data/lib/fluxbit/config/tab_component.rb +6 -0
- data/lib/fluxbit/config/table_component.rb +75 -0
- data/lib/fluxbit/config/theme_button_component.rb +19 -0
- data/lib/fluxbit/config/timeline_component.rb +77 -0
- data/lib/fluxbit/view_components/engine.rb +11 -3
- data/lib/fluxbit/view_components/version.rb +1 -1
- data/lib/fluxbit/view_components.rb +27 -1
- data/lib/generators/fluxbit/devise_views_generator.rb +116 -0
- data/lib/generators/fluxbit/pagy_generator.rb +39 -0
- data/lib/generators/fluxbit/scaffold_generator.rb +165 -0
- data/lib/generators/fluxbit/templates/_alert.html.erb.tt +1 -0
- data/lib/generators/fluxbit/templates/_flash.html.erb.tt +15 -0
- data/lib/generators/fluxbit/templates/_form.html.erb.tt +38 -0
- data/lib/generators/fluxbit/templates/_metadata.html.erb.tt +44 -0
- data/lib/generators/fluxbit/templates/controller.rb.tt +406 -0
- data/lib/generators/fluxbit/templates/create.turbo_stream.erb.tt +7 -0
- data/lib/generators/fluxbit/templates/destroy.turbo_stream.erb.tt +3 -0
- data/lib/generators/fluxbit/templates/destroy_all.turbo_stream.erb.tt +9 -0
- data/lib/generators/fluxbit/templates/devise_views/confirmations/new.html.erb +11 -0
- data/lib/generators/fluxbit/templates/devise_views/layouts/devise.html.erb +64 -0
- data/lib/generators/fluxbit/templates/devise_views/mailer/confirmation_instructions.html.erb +5 -0
- data/lib/generators/fluxbit/templates/devise_views/mailer/email_changed.html.erb +7 -0
- data/lib/generators/fluxbit/templates/devise_views/mailer/password_changed.html.erb +3 -0
- data/lib/generators/fluxbit/templates/devise_views/mailer/reset_password_instructions.html.erb +8 -0
- data/lib/generators/fluxbit/templates/devise_views/mailer/unlock_instructions.html.erb +7 -0
- data/lib/generators/fluxbit/templates/devise_views/passwords/edit.html.erb +29 -0
- data/lib/generators/fluxbit/templates/devise_views/passwords/new.html.erb +11 -0
- data/lib/generators/fluxbit/templates/devise_views/registrations/edit.html.erb +43 -0
- data/lib/generators/fluxbit/templates/devise_views/registrations/new.html.erb +34 -0
- data/lib/generators/fluxbit/templates/devise_views/sessions/new.html.erb +15 -0
- data/lib/generators/fluxbit/templates/devise_views/shared/_error_messages.html.erb +14 -0
- data/lib/generators/fluxbit/templates/devise_views/shared/_links.html.erb +25 -0
- data/lib/generators/fluxbit/templates/devise_views/unlocks/new.html.erb +11 -0
- data/lib/generators/fluxbit/templates/edit.html.erb.tt +47 -0
- data/lib/generators/fluxbit/templates/fluxbit_pagy.css +27 -0
- data/lib/generators/fluxbit/templates/i18n.en.yml.tt +121 -0
- data/lib/generators/fluxbit/templates/i18n.pt-BR.yml.tt +121 -0
- data/lib/generators/fluxbit/templates/index.html.erb.tt +254 -0
- data/lib/generators/fluxbit/templates/index.json.jbuilder.tt +33 -0
- data/lib/generators/fluxbit/templates/new.html.erb.tt +47 -0
- data/lib/generators/fluxbit/templates/partial.html.erb.tt +61 -0
- data/lib/generators/fluxbit/templates/policy.rb.tt +36 -0
- data/lib/generators/fluxbit/templates/send_alert_via_drawer.erb.tt +10 -0
- data/lib/generators/fluxbit/templates/show.html.erb.tt +44 -0
- data/lib/generators/fluxbit/templates/show.json.jbuilder.tt +6 -0
- data/lib/generators/fluxbit/templates/update.turbo_stream.erb.tt +10 -0
- data/lib/generators/fluxbit/templates/update_all.turbo_stream.erb.tt +20 -0
- data/lib/install/install.rb +61 -3
- metadata +127 -35
- data/LICENSE.txt +0 -20
- data/app/components/fluxbit/form/checkbox_input_component.rb +0 -61
- data/app/components/fluxbit/form/datepicker_component.rb +0 -7
- data/app/components/fluxbit/form/radio_input_component.rb +0 -21
- data/app/components/fluxbit/form/range_input_component.rb +0 -51
- data/app/components/fluxbit/form/select_free_input_component.rb +0 -77
- data/app/components/fluxbit/form/select_input_component.rb +0 -21
- data/app/components/fluxbit/form/spacer_input_component.rb +0 -12
- data/app/components/fluxbit/form/text_input_component.rb +0 -225
- data/app/components/fluxbit/form/textarea_input_component.rb +0 -57
- data/app/components/fluxbit/form/toggle_input_component.rb +0 -166
- data/app/components/fluxbit/form/upload_image_input_component.html.erb +0 -48
- data/app/components/fluxbit/form/upload_image_input_component.rb +0 -61
- data/app/components/fluxbit/form/upload_input_component.html.erb +0 -12
- data/app/components/fluxbit/form/upload_input_component.rb +0 -47
- data/app/helpers/fluxbit/classes_helper.rb +0 -9
|
@@ -6,32 +6,36 @@ class Fluxbit::Form::Component < Fluxbit::Component
|
|
|
6
6
|
return @id ||= random_id if @props[:id].nil? && @form.nil?
|
|
7
7
|
return @props[:id] unless @props[:id].nil?
|
|
8
8
|
|
|
9
|
-
"#{@form.object_name}_#{@
|
|
9
|
+
"#{@form.object_name}_#{@attribute}"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def
|
|
13
|
-
return nil if
|
|
12
|
+
def define_help_text(help_text, object, attribute)
|
|
13
|
+
return nil if help_text.is_a? FalseClass
|
|
14
14
|
|
|
15
|
-
if
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
scope: [
|
|
15
|
+
if help_text.nil? && !object.nil? && !attribute.nil?
|
|
16
|
+
help_text = I18n.t(
|
|
17
|
+
attribute,
|
|
18
|
+
scope: [ object.class.name.pluralize.underscore.to_sym, :help_text ],
|
|
19
19
|
default: nil
|
|
20
20
|
)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
(
|
|
23
|
+
(help_text.is_a?(Array) ? help_text : [ help_text ]) + errors
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def define_helper_popover(helper_popover, object,
|
|
27
|
-
return
|
|
26
|
+
def define_helper_popover(helper_popover, object, attribute)
|
|
27
|
+
return nil if helper_popover == false
|
|
28
|
+
return helper_popover if !helper_popover.nil? || object.nil?
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
I18n.t(field, scope: [ :activerecord, :helper_popover, object_name ], default: nil)
|
|
30
|
+
I18n.t(attribute, scope: [ object.class.name.pluralize.underscore.to_sym, :helper_popover ], default: nil)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def label_value(label, object,
|
|
34
|
-
|
|
33
|
+
def label_value(label, object, attribute, id)
|
|
34
|
+
if label.nil? && !object.nil? && !attribute.nil?
|
|
35
|
+
key = [ object.class.name.pluralize.underscore.to_sym, :fields, attribute ]
|
|
36
|
+
return I18n.exists?(key) ? I18n.t(attribute, scope: key[0..-2]) : object.class.human_attribute_name(attribute)
|
|
37
|
+
end
|
|
38
|
+
return attribute.to_s.humanize if label.nil? && object.nil?
|
|
35
39
|
return id.to_s.humanize if label.nil? && !id.nil?
|
|
36
40
|
return label unless label.nil?
|
|
37
41
|
|
|
@@ -46,27 +50,24 @@ class Fluxbit::Form::Component < Fluxbit::Component
|
|
|
46
50
|
color: @color,
|
|
47
51
|
helper_popover: @helper_popover,
|
|
48
52
|
helper_popover_placement: @helper_popover_placement,
|
|
49
|
-
class: @label_class
|
|
53
|
+
class: @label_class,
|
|
54
|
+
required: @required
|
|
50
55
|
).with_content(@label).render_in(view_context)
|
|
51
56
|
end
|
|
52
57
|
|
|
53
58
|
def errors
|
|
54
59
|
return [] unless @object&.errors&.any?
|
|
55
60
|
|
|
56
|
-
@object.errors.filter { |f| f.attribute == @
|
|
61
|
+
@object.errors.filter { |f| f.attribute == @attribute }.map(&:full_message)
|
|
57
62
|
end
|
|
58
63
|
|
|
59
|
-
def
|
|
60
|
-
return "" if @
|
|
61
|
-
|
|
62
|
-
# safe_join(
|
|
63
|
-
# @helper_text.compact.map do |text|
|
|
64
|
-
# Fluxbit::HelperTextComponent.new(color: @color).with_content(text).render_in(view_context)
|
|
65
|
-
# end
|
|
66
|
-
# )
|
|
64
|
+
def help_text
|
|
65
|
+
return "" if @help_text.blank? || @help_text.compact.blank?
|
|
67
66
|
|
|
68
|
-
@
|
|
69
|
-
|
|
67
|
+
nodes = @help_text.compact.map do |text|
|
|
68
|
+
Fluxbit::Form::HelpTextComponent.new(color: @color).with_content(text).render_in(view_context)
|
|
70
69
|
end
|
|
70
|
+
|
|
71
|
+
view_context.safe_join(nodes)
|
|
71
72
|
end
|
|
72
73
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<%= content_tag :div, **@wrapper_html do %>
|
|
2
|
+
<div class="<%= self.styles[:base] %>">
|
|
3
|
+
<label for="<%= id %>" class="<%= self.styles[:label] %> <%= self.styles[:height][@height] %>">
|
|
4
|
+
<div class="<%= self.styles[:inner_div] %>">
|
|
5
|
+
<% if content? %>
|
|
6
|
+
<%= content %>
|
|
7
|
+
<% else %>
|
|
8
|
+
<%= create_icon %>
|
|
9
|
+
|
|
10
|
+
<% if @title != false %>
|
|
11
|
+
<p class="<%= self.styles[:title] %>">
|
|
12
|
+
<% if @title != true %>
|
|
13
|
+
<%= @title %>
|
|
14
|
+
<% else %>
|
|
15
|
+
<span class="font-semibold">Click to upload</span> or drag and drop
|
|
16
|
+
<% end %>
|
|
17
|
+
</p>
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
<% if @subtitle != false %>
|
|
21
|
+
<p class="<%= self.styles[:subtitle] %>">
|
|
22
|
+
<% if @subtitle != true %>
|
|
23
|
+
<%= @subtitle %>
|
|
24
|
+
<% else %>
|
|
25
|
+
SVG, PNG, JPG or GIF (MAX. 800x400px)
|
|
26
|
+
<% end %>
|
|
27
|
+
</p>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
<% if @form.present? && @attribute.present? %>
|
|
32
|
+
<%= @form.file_field(@attribute, **@props) %>
|
|
33
|
+
<% else %>
|
|
34
|
+
<%= file_field_tag(@name, **@props) %>
|
|
35
|
+
<% end %>
|
|
36
|
+
</label>
|
|
37
|
+
</div>
|
|
38
|
+
<%= help_text %>
|
|
39
|
+
<% end %>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::Form::DropzoneComponent` provides a drag-and-drop file input zone with support for labels,
|
|
4
|
+
# titles, subtitles, icons, validation states, and integration with Rails form builders.
|
|
5
|
+
# It renders a visually rich area that lets users drag files or click to select a file, and is fully customizable
|
|
6
|
+
# via its options and slot for custom content.
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# = render Fluxbit::Form::DropzoneComponent.new(name: :avatar)
|
|
10
|
+
#
|
|
11
|
+
# @see docs/03_Forms/Dropzone.md For detailed documentation and examples.
|
|
12
|
+
class Fluxbit::Form::DropzoneComponent < Fluxbit::Form::FieldComponent
|
|
13
|
+
include Fluxbit::Config::Form::DropzoneComponent
|
|
14
|
+
|
|
15
|
+
# Initializes the dropzone component with the given properties.
|
|
16
|
+
#
|
|
17
|
+
# @param name [String] Name of the field (required unless using form builder)
|
|
18
|
+
# @param label [String] Label for the input (optional)
|
|
19
|
+
# @param title [Boolean, String] Title text above the dropzone (true for default, false to hide, or custom string)
|
|
20
|
+
# @param subtitle [Boolean, String] Subtitle text below the title (true for default, false to hide, or custom string)
|
|
21
|
+
# @param icon [String, Symbol] Icon to display above the title (defaults to config)
|
|
22
|
+
# @param icon_html [Hash] Extra props for the icon element
|
|
23
|
+
# @param height [Integer] Height preset (0: auto, 1: h-32, 2: h-64, 3: h-96; default is 0)
|
|
24
|
+
# @param help_text [String] Helper or error text below the field
|
|
25
|
+
# @param ... any other HTML attribute supported by file_field_tag
|
|
26
|
+
def initialize(**props)
|
|
27
|
+
super(**props)
|
|
28
|
+
@title = options(@props.delete(:title), default: true)
|
|
29
|
+
@subtitle = options(@props.delete(:subtitle), default: true)
|
|
30
|
+
@icon = @props.delete(:icon) || @@icon
|
|
31
|
+
@icon_html = @props.delete(:icon_html) || { class: styles[:icon] }
|
|
32
|
+
@height = @props.delete(:height) || @@height
|
|
33
|
+
add to: @props, class: "hidden"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def create_icon
|
|
37
|
+
anyicon(@icon, class: styles[:icon])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Fluxbit::Form::FieldComponent < Fluxbit::Form::Component
|
|
4
|
+
def initialize(**props)
|
|
5
|
+
super
|
|
6
|
+
@props = props
|
|
7
|
+
@form = @props.delete(:form)
|
|
8
|
+
@attribute = @props.delete(:attribute)
|
|
9
|
+
@name = @props.delete(:name) || (@attribute if @form.present?)
|
|
10
|
+
@value = @props.delete(:value)
|
|
11
|
+
@id = @props.delete(:id)
|
|
12
|
+
@required = @props.delete(:required)
|
|
13
|
+
|
|
14
|
+
@object = @form&.object
|
|
15
|
+
@help_text = define_help_text(props.delete(:help_text), @object, @attribute)
|
|
16
|
+
@helper_popover = define_helper_popover(props.delete(:helper_popover), @object, @attribute)
|
|
17
|
+
@helper_popover_placement = props.delete(:helper_popover_placement) || "right"
|
|
18
|
+
@label = label_value(props.delete(:label), @object, @attribute, @id)
|
|
19
|
+
@wrapper_html = props.delete(:wrapper_html) || {}
|
|
20
|
+
@wrapper_html = { class: @wrapper_html } if @wrapper_html.is_a?(String)
|
|
21
|
+
define_wrapper_options
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def define_wrapper_options
|
|
25
|
+
add(to: @wrapper_html, class: "required") if @required.present?
|
|
26
|
+
add(to: @wrapper_html, class: @name.to_s) if @name.present?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -89,7 +89,7 @@ class Fluxbit::Form::FormBuilderComponent < Fluxbit::Component
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def element_type(type)
|
|
92
|
-
return "
|
|
92
|
+
return "TextField" if type.nil? || type.to_s.in?(TEXT_TYPES)
|
|
93
93
|
return type.to_s.concat("_input").camelcase if type.to_s.in?(INPUT_TYPES)
|
|
94
94
|
|
|
95
95
|
case type
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# The `Fluxbit::
|
|
3
|
+
# The `Fluxbit::HelpTextComponent` is a component for rendering customizable helper text elements.
|
|
4
4
|
# It extends `Fluxbit::Component` and provides options for configuring the helper text's
|
|
5
5
|
# appearance and behavior. You can control the helper text's color and other attributes.
|
|
6
6
|
# The helper text can have various styles applied based on the provided properties.
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# = render Fluxbit::Form::HelpTextComponent.new { "Your password must be at least 8 characters." }
|
|
10
|
+
#
|
|
11
|
+
# @see docs/03_Forms/HelpText.md For detailed documentation and examples.
|
|
12
|
+
class Fluxbit::Form::HelpTextComponent < Fluxbit::Form::Component
|
|
13
|
+
include Fluxbit::Config::Form::HelpTextComponent
|
|
9
14
|
|
|
10
15
|
# Initializes the helper text component with the given properties.
|
|
11
16
|
#
|
|
@@ -15,7 +20,7 @@ class Fluxbit::Form::HelperTextComponent < Fluxbit::Form::Component
|
|
|
15
20
|
def initialize(color: nil, **props)
|
|
16
21
|
super
|
|
17
22
|
@props = props
|
|
18
|
-
color = @@color unless color.in?
|
|
23
|
+
color = @@color unless color.in? styles[:colors].keys
|
|
19
24
|
add class: style(color), to: @props, first_element: true
|
|
20
25
|
end
|
|
21
26
|
|
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# The `Fluxbit::Form::LabelComponent` is a flexible and accessible label for form fields.
|
|
4
|
+
# It supports custom content, helper popovers, multiple color styles, sizing options, and
|
|
5
|
+
# displays associated help text when provided. It is fully compatible with Rails form builders.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# = render Fluxbit::Form::LabelComponent.new(with_content: "Your Name")
|
|
9
|
+
#
|
|
10
|
+
# @see docs/03_Forms/Label.md For detailed documentation and examples.
|
|
3
11
|
class Fluxbit::Form::LabelComponent < Fluxbit::Form::Component
|
|
4
|
-
|
|
5
|
-
{
|
|
6
|
-
base: "flex font-medium",
|
|
7
|
-
colors: {
|
|
8
|
-
default: "text-gray-900 dark:text-white",
|
|
9
|
-
success: "text-green-700 dark:text-green-500",
|
|
10
|
-
failure: "text-red-700 dark:text-red-500",
|
|
11
|
-
info: "text-cyan-500 dark:text-cyan-600",
|
|
12
|
-
warning: "text-yellow-500 dark:text-yellow-600"
|
|
13
|
-
},
|
|
14
|
-
sizes: {
|
|
15
|
-
sm: "text-sm",
|
|
16
|
-
md: "text-md",
|
|
17
|
-
lg: "text-lg"
|
|
18
|
-
},
|
|
19
|
-
helper_popover: "px-2 text-slate-400"
|
|
20
|
-
}
|
|
21
|
-
end
|
|
12
|
+
include Fluxbit::Config::Form::LabelComponent
|
|
22
13
|
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
# Initializes the label component with the given properties.
|
|
15
|
+
#
|
|
16
|
+
# @param with_content [String] The label text to display (alternative to block content)
|
|
17
|
+
# @param help_text [String, Array<String>] One or more help text messages to render below the label
|
|
18
|
+
# @param helper_popover [String] Popover content shown on icon hover
|
|
19
|
+
# @param helper_popover_placement [String] Placement of the popover (default: "right")
|
|
20
|
+
# @param sizing [Integer] Size index for label text (default: config default)
|
|
21
|
+
# @param color [Symbol] Label color (:default, :success, :danger, :info, :warning)
|
|
22
|
+
# @param class [String] Additional CSS classes for the label element
|
|
23
|
+
# @param ... any other HTML attribute supported by the <label> tag
|
|
24
|
+
def initialize(**props)
|
|
25
25
|
super
|
|
26
26
|
@props = props
|
|
27
|
-
@
|
|
28
|
-
@
|
|
29
|
-
@
|
|
30
|
-
@helper_popover = helper_popover
|
|
31
|
-
@helper_popover_placement = helper_popover_placement
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
@with_content = @props.delete(:with_content)
|
|
28
|
+
@help_text = @props.delete(:help_text)
|
|
29
|
+
@help_text = [ @help_text ] if !@help_text.is_a?(Array)
|
|
30
|
+
@helper_popover = @props.delete(:helper_popover)
|
|
31
|
+
@helper_popover_placement = @props.delete(:helper_popover_placement) || @@helper_popover_placement
|
|
32
|
+
@sizing = @props[:sizing].to_i || @@sizing
|
|
33
|
+
@sizing = (styles[:sizes].count - 1) if @sizing > (styles[:sizes].count - 1)
|
|
34
|
+
@color = options(@props.delete(:color), collection: styles[:colors], default: @@color)
|
|
35
|
+
@required = @props.delete(:required) || false
|
|
36
|
+
|
|
37
|
+
add class: styles[:colors][@color], to: @props, first_element: true
|
|
34
38
|
add class: styles[:base], to: @props, first_element: true
|
|
35
39
|
add class: styles[:sizes][@sizing], to: @props, first_element: true
|
|
36
40
|
end
|
|
@@ -39,7 +43,7 @@ class Fluxbit::Form::LabelComponent < Fluxbit::Form::Component
|
|
|
39
43
|
return "" if @helper_popover.nil?
|
|
40
44
|
|
|
41
45
|
content_tag :span,
|
|
42
|
-
anyicon(
|
|
46
|
+
anyicon(@@helper_popover_icon, class: @@helper_popover_icon_class),
|
|
43
47
|
{
|
|
44
48
|
"data-popover-placement": @helper_popover_placement,
|
|
45
49
|
"data-popover-target": target,
|
|
@@ -56,10 +60,16 @@ class Fluxbit::Form::LabelComponent < Fluxbit::Form::Component
|
|
|
56
60
|
def call
|
|
57
61
|
safe_join(
|
|
58
62
|
[
|
|
59
|
-
content_tag(:label, safe_join([ content || @with_content, span_helper_popover ]), @props),
|
|
60
|
-
|
|
63
|
+
content_tag(:label, safe_join([ content || @with_content, span_helper_popover, required ]), @props),
|
|
64
|
+
help_text,
|
|
61
65
|
render_popover
|
|
62
66
|
]
|
|
63
67
|
)
|
|
64
68
|
end
|
|
69
|
+
|
|
70
|
+
def required
|
|
71
|
+
return "" unless @required
|
|
72
|
+
|
|
73
|
+
content_tag(:span, "*", class: styles[:required])
|
|
74
|
+
end
|
|
65
75
|
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::Form::PasswordComponent` is a password input component that extends `Fluxbit::Form::TextFieldComponent`.
|
|
4
|
+
# It provides a password field with a toggleable visibility icon (eye/eye-slash) and optional password strength indicators.
|
|
5
|
+
# The component can display validation checks for password requirements such as length, letters, capital letters, numbers, and special characters.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# = render Fluxbit::Form::PasswordComponent.new(name: :password)
|
|
9
|
+
#
|
|
10
|
+
# @example With password strength checks
|
|
11
|
+
# = render Fluxbit::Form::PasswordComponent.new(name: :password, show_strength: true)
|
|
12
|
+
#
|
|
13
|
+
# @example Custom requirements
|
|
14
|
+
# = render Fluxbit::Form::PasswordComponent.new(
|
|
15
|
+
# name: :password,
|
|
16
|
+
# show_strength: true,
|
|
17
|
+
# min_length: 12,
|
|
18
|
+
# require_uppercase: true,
|
|
19
|
+
# require_lowercase: true,
|
|
20
|
+
# require_numbers: true,
|
|
21
|
+
# require_special: true
|
|
22
|
+
# )
|
|
23
|
+
#
|
|
24
|
+
# @see docs/03_Forms/Password.md For detailed documentation and examples.
|
|
25
|
+
class Fluxbit::Form::PasswordComponent < Fluxbit::Form::TextFieldComponent
|
|
26
|
+
# Initializes the password field component with the given properties.
|
|
27
|
+
#
|
|
28
|
+
# @param form [ActionView::Helpers::FormBuilder] The form builder (optional, for Rails forms)
|
|
29
|
+
# @param attribute [Symbol] The model attribute to be used in the form (required if using form builder)
|
|
30
|
+
# @param show_strength [Boolean] Whether to display password strength indicators (default: false)
|
|
31
|
+
# @param min_length [Integer] Minimum password length requirement (default: 8)
|
|
32
|
+
# @param require_uppercase [Boolean] Require at least one uppercase letter (default: true)
|
|
33
|
+
# @param require_lowercase [Boolean] Require at least one lowercase letter (default: true)
|
|
34
|
+
# @param require_numbers [Boolean] Require at least one number (default: true)
|
|
35
|
+
# @param require_special [Boolean] Require at least one special character (default: false)
|
|
36
|
+
# @param strength_labels [Hash] Custom labels for strength checks
|
|
37
|
+
# @param ... any other options supported by TextFieldComponent
|
|
38
|
+
def initialize(**props)
|
|
39
|
+
@show_strength = props.delete(:show_strength) || false
|
|
40
|
+
@min_length = props.delete(:min_length) || 8
|
|
41
|
+
|
|
42
|
+
# Get boolean values, defaulting to true for uppercase/lowercase/numbers, false for special
|
|
43
|
+
uppercase_val = props.delete(:require_uppercase)
|
|
44
|
+
@require_uppercase = uppercase_val.nil? ? true : uppercase_val
|
|
45
|
+
|
|
46
|
+
lowercase_val = props.delete(:require_lowercase)
|
|
47
|
+
@require_lowercase = lowercase_val.nil? ? true : lowercase_val
|
|
48
|
+
|
|
49
|
+
numbers_val = props.delete(:require_numbers)
|
|
50
|
+
@require_numbers = numbers_val.nil? ? true : numbers_val
|
|
51
|
+
|
|
52
|
+
@require_special = props.delete(:require_special) || false
|
|
53
|
+
@strength_labels = props.delete(:strength_labels) || default_strength_labels
|
|
54
|
+
|
|
55
|
+
# Force type to password and add eye icon for visibility toggle
|
|
56
|
+
props[:type] = :password
|
|
57
|
+
props[:right_icon] ||= :"heroicons_outline:eye"
|
|
58
|
+
|
|
59
|
+
# Add data attributes for Stimulus controller
|
|
60
|
+
props[:data] ||= {}
|
|
61
|
+
# props[:data][:controller] = add_controller(props[:data][:controller], "fx-password")
|
|
62
|
+
props[:data][:action] = add_action(props[:data][:action], "input->fx-password#validate")
|
|
63
|
+
|
|
64
|
+
# Store right_icon_html for later use in create_right_icon
|
|
65
|
+
@password_right_icon_html = props.delete(:right_icon_html) || {}
|
|
66
|
+
|
|
67
|
+
# Set up wrapper_html with controller before calling super
|
|
68
|
+
props[:wrapper_html] ||= {}
|
|
69
|
+
props[:wrapper_html][:data] ||= {}
|
|
70
|
+
props[:wrapper_html][:data][:controller] = "fx-password"
|
|
71
|
+
props[:wrapper_html][:data].merge!(
|
|
72
|
+
{
|
|
73
|
+
"fx-password-target": "inputWrapper",
|
|
74
|
+
"fx-password-min-length-value": @min_length,
|
|
75
|
+
"fx-password-require-uppercase-value": @require_uppercase,
|
|
76
|
+
"fx-password-require-lowercase-value": @require_lowercase,
|
|
77
|
+
"fx-password-require-numbers-value": @require_numbers,
|
|
78
|
+
"fx-password-require-special-value": @require_special
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
super(**props)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def call
|
|
87
|
+
content_tag :div, **@wrapper_html do
|
|
88
|
+
safe_join [
|
|
89
|
+
label,
|
|
90
|
+
icon_container_wrapper,
|
|
91
|
+
help_text,
|
|
92
|
+
(@show_strength ? strength_indicator : nil)
|
|
93
|
+
].compact
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def password_styles
|
|
100
|
+
@password_styles ||= {
|
|
101
|
+
strength_wrapper: "mt-2 space-y-2",
|
|
102
|
+
strength_bar_wrapper: "space-y-1",
|
|
103
|
+
strength_bar_label: "text-sm font-medium text-slate-700 dark:text-slate-300",
|
|
104
|
+
strength_bar_container: "w-full bg-slate-200 rounded-full h-2 dark:bg-slate-700",
|
|
105
|
+
strength_bar: "h-2 rounded-full transition-all duration-300 bg-slate-300 dark:bg-slate-600",
|
|
106
|
+
checks_list: "space-y-1",
|
|
107
|
+
check_item: "flex items-center gap-2 text-sm text-slate-600 dark:text-slate-400",
|
|
108
|
+
check_icon: "flex-shrink-0 text-red-500 dark:text-red-400",
|
|
109
|
+
check_label: ""
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def icon_container_wrapper
|
|
114
|
+
content_tag :div,
|
|
115
|
+
data: {
|
|
116
|
+
"fx-password-target": "inputWrapper",
|
|
117
|
+
"fx-password-min-length-value": @min_length,
|
|
118
|
+
"fx-password-require-uppercase-value": @require_uppercase,
|
|
119
|
+
"fx-password-require-lowercase-value": @require_lowercase,
|
|
120
|
+
"fx-password-require-numbers-value": @require_numbers,
|
|
121
|
+
"fx-password-require-special-value": @require_special
|
|
122
|
+
} do
|
|
123
|
+
icon_container
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def default_strength_labels
|
|
128
|
+
{
|
|
129
|
+
length: I18n.t("fluxbit.form.password.checks.length", default: "At least %{count} characters", count: @min_length),
|
|
130
|
+
uppercase: I18n.t("fluxbit.form.password.checks.uppercase", default: "Contains uppercase letter"),
|
|
131
|
+
lowercase: I18n.t("fluxbit.form.password.checks.lowercase", default: "Contains lowercase letter"),
|
|
132
|
+
numbers: I18n.t("fluxbit.form.password.checks.numbers", default: "Contains number"),
|
|
133
|
+
special: I18n.t("fluxbit.form.password.checks.special", default: "Contains special character"),
|
|
134
|
+
strength: I18n.t("fluxbit.form.password.strength", default: "Password strength")
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def strength_indicator
|
|
139
|
+
content_tag :div, class: password_styles[:strength_wrapper], data: { "fx-password-target": "strengthIndicator" } do
|
|
140
|
+
safe_join [
|
|
141
|
+
strength_bar,
|
|
142
|
+
strength_checks
|
|
143
|
+
]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def strength_bar
|
|
148
|
+
content_tag :div, class: password_styles[:strength_bar_wrapper] do
|
|
149
|
+
safe_join([
|
|
150
|
+
content_tag(:div, class: password_styles[:strength_bar_label]) do
|
|
151
|
+
content_tag :span, @strength_labels[:strength]
|
|
152
|
+
end,
|
|
153
|
+
content_tag(:div, class: password_styles[:strength_bar_container]) do
|
|
154
|
+
content_tag :div,
|
|
155
|
+
"",
|
|
156
|
+
class: password_styles[:strength_bar],
|
|
157
|
+
data: { "fx-password-target": "strengthBar" }
|
|
158
|
+
end
|
|
159
|
+
])
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def strength_checks
|
|
164
|
+
content_tag :ul, class: password_styles[:checks_list] do
|
|
165
|
+
safe_join [
|
|
166
|
+
strength_check(:length, @strength_labels[:length]),
|
|
167
|
+
(@require_lowercase ? strength_check(:lowercase, @strength_labels[:lowercase]) : nil),
|
|
168
|
+
(@require_uppercase ? strength_check(:uppercase, @strength_labels[:uppercase]) : nil),
|
|
169
|
+
(@require_numbers ? strength_check(:numbers, @strength_labels[:numbers]) : nil),
|
|
170
|
+
(@require_special ? strength_check(:special, @strength_labels[:special]) : nil)
|
|
171
|
+
].compact
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def strength_check(type, label)
|
|
176
|
+
content_tag :li,
|
|
177
|
+
class: password_styles[:check_item],
|
|
178
|
+
data: { "fx-password-target": "check#{type.to_s.capitalize}" } do
|
|
179
|
+
safe_join([
|
|
180
|
+
# Icon container with both check and X icons
|
|
181
|
+
content_tag(:span, class: password_styles[:check_icon]) do
|
|
182
|
+
safe_join([
|
|
183
|
+
# X icon (visible by default - password requirement not met)
|
|
184
|
+
content_tag(:span,
|
|
185
|
+
data: { "fx-password-target": "check#{type.to_s.capitalize}Fail" }
|
|
186
|
+
) do
|
|
187
|
+
anyicon(:"heroicons_solid:x-circle", class: "size-4")
|
|
188
|
+
end,
|
|
189
|
+
# Check icon (hidden by default - password requirement met)
|
|
190
|
+
content_tag(:span,
|
|
191
|
+
class: "hidden",
|
|
192
|
+
data: { "fx-password-target": "check#{type.to_s.capitalize}Pass" }
|
|
193
|
+
) do
|
|
194
|
+
anyicon(:"heroicons_solid:check-circle", class: "size-4")
|
|
195
|
+
end
|
|
196
|
+
])
|
|
197
|
+
end,
|
|
198
|
+
content_tag(:span, label, class: password_styles[:check_label])
|
|
199
|
+
])
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def add_controller(existing, new_controller)
|
|
204
|
+
existing ? "#{existing} #{new_controller}" : new_controller
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def add_action(existing, new_action)
|
|
208
|
+
existing ? "#{existing} #{new_action}" : new_action
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def add_class(existing, new_class)
|
|
212
|
+
existing ? "#{existing} #{new_class}" : new_class
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Override to render both eye icons with toggle visibility
|
|
216
|
+
def create_right_icon
|
|
217
|
+
return "" if @right_icon.nil?
|
|
218
|
+
|
|
219
|
+
# Build wrapper attributes
|
|
220
|
+
wrapper_attrs = {
|
|
221
|
+
class: add_class(@password_right_icon_html[:class], "absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer"),
|
|
222
|
+
onclick: "", # Prevent pointer-events-none
|
|
223
|
+
data: {
|
|
224
|
+
action: "click->fx-password#toggleVisibility"
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
content_tag :div, **wrapper_attrs do
|
|
229
|
+
safe_join([
|
|
230
|
+
# Eye icon (visible by default)
|
|
231
|
+
content_tag(:div,
|
|
232
|
+
class: "#{styles[:additional_icons][:class][@color]}",
|
|
233
|
+
data: { "fx-password-target": "eyeIcon" }
|
|
234
|
+
) do
|
|
235
|
+
eye_icon
|
|
236
|
+
end,
|
|
237
|
+
# Eye-slash icon (hidden by default)
|
|
238
|
+
content_tag(:div,
|
|
239
|
+
class: "#{styles[:additional_icons][:class][@color]} hidden",
|
|
240
|
+
data: { "fx-password-target": "eyeSlashIcon" }
|
|
241
|
+
) do
|
|
242
|
+
eye_slash_icon
|
|
243
|
+
end
|
|
244
|
+
])
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|