fluxbit_view_components 0.1.0 → 0.3.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/app/components/fluxbit/form/check_box_component.rb +56 -0
  4. data/app/components/fluxbit/form/component.rb +20 -25
  5. data/app/components/fluxbit/form/dropzone_component.html.erb +39 -0
  6. data/app/components/fluxbit/form/dropzone_component.rb +39 -0
  7. data/app/components/fluxbit/form/field_component.rb +26 -0
  8. data/app/components/fluxbit/form/form_builder_component.rb +5 -7
  9. data/app/components/fluxbit/form/{helper_text_component.rb → help_text_component.rb} +8 -3
  10. data/app/components/fluxbit/form/label_component.rb +32 -29
  11. data/app/components/fluxbit/form/range_component.rb +52 -0
  12. data/app/components/fluxbit/form/select_component.rb +88 -0
  13. data/app/components/fluxbit/form/text_field_component.rb +168 -0
  14. data/app/components/fluxbit/form/toggle_component.html.erb +23 -0
  15. data/app/components/fluxbit/form/toggle_component.rb +81 -0
  16. data/app/components/fluxbit/form/upload_image_component.html.erb +50 -0
  17. data/app/components/fluxbit/form/upload_image_component.rb +50 -0
  18. data/app/helpers/fluxbit/classes_helper.rb +0 -12
  19. data/app/helpers/fluxbit/components_helper.rb +23 -51
  20. data/app/helpers/fluxbit/form_builder.rb +87 -0
  21. data/lib/fluxbit/config/form/check_box_component.rb +19 -0
  22. data/lib/fluxbit/config/form/dropzone_component.rb +20 -0
  23. data/lib/fluxbit/config/form/{helper_text_component.rb → help_text_component.rb} +1 -1
  24. data/lib/fluxbit/config/form/label_component.rb +30 -0
  25. data/lib/fluxbit/config/form/range_component.rb +15 -0
  26. data/lib/fluxbit/config/form/text_field_component.rb +76 -0
  27. data/{app/components/fluxbit/form/toggle_input_component.rb → lib/fluxbit/config/form/toggle_component.rb} +28 -115
  28. data/lib/fluxbit/templates/darkmode.js.template +7 -0
  29. data/lib/fluxbit/templates/tailwind.config.js.template +4 -0
  30. data/lib/fluxbit/view_components/version.rb +1 -3
  31. data/lib/fluxbit/view_components.rb +7 -1
  32. data/lib/install/install.rb +57 -47
  33. data/lib/tasks/fluxbit_view_components_tasks.rake +30 -10
  34. metadata +25 -25
  35. data/LICENSE.txt +0 -20
  36. data/app/components/fluxbit/form/checkbox_input_component.rb +0 -61
  37. data/app/components/fluxbit/form/datepicker_component.rb +0 -7
  38. data/app/components/fluxbit/form/radio_input_component.rb +0 -21
  39. data/app/components/fluxbit/form/range_input_component.rb +0 -51
  40. data/app/components/fluxbit/form/select_free_input_component.rb +0 -77
  41. data/app/components/fluxbit/form/select_input_component.rb +0 -21
  42. data/app/components/fluxbit/form/spacer_input_component.rb +0 -12
  43. data/app/components/fluxbit/form/text_input_component.rb +0 -225
  44. data/app/components/fluxbit/form/textarea_input_component.rb +0 -57
  45. data/app/components/fluxbit/form/upload_image_input_component.html.erb +0 -48
  46. data/app/components/fluxbit/form/upload_image_input_component.rb +0 -66
  47. data/app/components/fluxbit/form/upload_input_component.html.erb +0 -12
  48. data/app/components/fluxbit/form/upload_input_component.rb +0 -47
  49. data/lib/fluxbit/view_components/codemods/v3_slot_setters.rb +0 -222
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16de56192c4f954e22b5391bbd8e94970a8345ede43ffaf8866050222a8d486a
4
- data.tar.gz: fb3a6c03297fc0836ee3c61005e530cc32640e0c97dbc872945b661e6e2b95c6
3
+ metadata.gz: 5f5ca4fde5d32556cdbc0f25088912fa549531dea43e9e46557806c742a61a3c
4
+ data.tar.gz: 2feca2ea7274688b6d1e850aee6dc6d5638718718584e977514f0abf96cedd8e
5
5
  SHA512:
6
- metadata.gz: 9127a488dc9bec5cc886886855edb7649e62a9ce36406e0c9aef4f956eb392fd9f21f42473459885eeb9758f19528c3185bd8729398852d91e17a27168a6cb40
7
- data.tar.gz: 3bced6391398780c7877563c35f406ede7c4dc838c2ac9e410c99db815c1c58686d79aa034eff979c561010f191701329a7daa9c857ed06b521b6441ff9c502d
6
+ metadata.gz: 85ef792f0b9ead019bf89459531723043c7b771fe7b6c8f016ba03697d2e80d93139a081e0f4250d46545eb97b31f159fcaa9ba2bd5592c27053a0792c8063f5
7
+ data.tar.gz: da59d2153f6ea5bb116ee44ae2dd7fd8cd8631c9024ebe7972d5103eb58f1fc05012953386d4520aed4247970dc00fc810ede2afc08a0ecc8329db053b3d5cb6
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Fluxbit ViewComponents
2
2
 
3
+ ![Gem Downloads (for latest version)](https://img.shields.io/gem/dtv/fluxbit_view_components) ![GitHub License](https://img.shields.io/github/license/arthurmolina/fluxbit_view_components) ![GitHub last commit](https://img.shields.io/github/last-commit/arthurmolina/fluxbit_view_components) ![Gem Downloads (for latest version)](https://img.shields.io/gem/dtv/fluxbit_view_components) ![Gem Total Downloads](https://img.shields.io/gem/dt/fluxbit_view_components) ![GitHub forks](https://img.shields.io/github/forks/arthurmolina/fluxbit_view_components)
4
+
5
+
6
+
3
7
  Fluxbit ViewComponents is an implementation of the Fluxbit Design System using [ViewComponent](https://github.com/github/view_component).
4
8
 
5
9
  <div style="text-align: center;">
@@ -8,7 +12,9 @@ Fluxbit ViewComponents is an implementation of the Fluxbit Design System using [
8
12
 
9
13
  ## Preview
10
14
 
11
- I don't have a site to host the lookbook. Sorry.
15
+ We have a Lookbook app online to show the documentation and all the Components available in action!
16
+
17
+ Just [Click here!](https://fluxbit.artz.to)
12
18
 
13
19
  ## Usage
14
20
 
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Fluxbit::Form::CheckBoxComponent` is a form input component for check boxes and radio buttons.
4
+ # It extends `Fluxbit::Form::FieldComponent` and provides a styled checkbox/radio with label, helper text,
5
+ # and support for different visual states and groupings. It automatically adds the correct styles for both
6
+ # checkbox and radio types and works with or without Rails form builders.
7
+ #
8
+ # @example Basic usage
9
+ # = render Fluxbit::Form::CheckBoxComponent.new(name: :accept_terms, label: "Accept the terms")
10
+ #
11
+ # @see docs/03_Forms/CheckBox.md For detailed documentation and examples.
12
+ class Fluxbit::Form::CheckBoxComponent < Fluxbit::Form::FieldComponent
13
+ include Fluxbit::Config::Form::CheckBoxComponent
14
+ TYPE_DEFAULT = :check_box
15
+ TYPE_OPTIONS = %i[check_box checkbox radio_button].freeze
16
+
17
+ # Initializes the check box component with the given properties.
18
+ #
19
+ # @param name [String] Name of the field (required unless using form builder)
20
+ # @param label [String] Label text next to the input (optional)
21
+ # @param value [String] Value for the field (optional)
22
+ # @param type [String, Symbol] Input type (`"check_box"`, `"checkbox"`, `"radio_button"`)
23
+ # @param help_text [String] Helper or error text below the field
24
+ # @param disabled [Boolean] Disables the input if true
25
+ # @param checked [Boolean] Marks the input as checked if true
26
+ # @param class [String] Additional CSS classes for the input element
27
+ # @param ... any other HTML attribute supported by check_box_tag/radio_button_tag
28
+ def initialize(**props)
29
+ super(**props)
30
+ @type = options(@props.delete(:type), collection: TYPE_OPTIONS, default: TYPE_DEFAULT)
31
+ add(class: styles[:checkbox], to: @props, first_element: true) if @props[:type] == "checkbox"
32
+ add(class: styles[:base], to: @props, first_element: true)
33
+ end
34
+
35
+ def input
36
+ if @form.present? && @attribute.present?
37
+ @form.public_send(@type, @attribute, @props)
38
+ else
39
+ public_send("#{@type}_tag", @name, @value, @props)
40
+ end
41
+ end
42
+
43
+ def call
44
+ if @help_text
45
+ content_tag :div, { class: "flex" } do
46
+ concat content_tag(:div, input, { class: styles[:input_div] })
47
+ concat content_tag(:div, safe_join([ label, help_text ]), { class: styles[:helper_div] })
48
+ end
49
+ else
50
+ content_tag :div, { class: styles[:no_helper_div] } do
51
+ concat input
52
+ concat label
53
+ end
54
+ end
55
+ end
56
+ end
@@ -6,31 +6,32 @@ 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}_#{@field}"
9
+ "#{@form.object_name}_#{@attribute}"
10
10
  end
11
11
 
12
- def define_helper_text(helper_text, object, field)
13
- return nil if helper_text.is_a? FalseClass
12
+ def define_help_text(help_text, object, attribute)
13
+ return nil if help_text.is_a? FalseClass
14
14
 
15
- if helper_text.nil? && !object.nil? && !field.nil?
16
- helper_text = I18n.t(
17
- field,
18
- scope: [ :activerecord, :helper_text, object.class.name.underscore.to_sym ],
15
+ if help_text.nil? && !object.nil? && !attribute.nil?
16
+ help_text = I18n.t(
17
+ attribute,
18
+ scope: [ :activerecord, :help_text, object.class.name.underscore.to_sym ],
19
19
  default: nil
20
20
  )
21
21
  end
22
22
 
23
- (helper_text.is_a?(Array) ? helper_text : [ helper_text ]) + errors
23
+ (help_text.is_a?(Array) ? help_text : [ help_text ]) + errors
24
24
  end
25
25
 
26
- def define_helper_popover(helper_popover, object, field)
27
- return helper_popover if helper_popover != false && !helper_popover.nil?
26
+ def define_helper_popover(helper_popover, object, attribute)
27
+ return helper_popover if (helper_popover != false && !helper_popover.nil?) || object.nil?
28
28
 
29
- I18n.t(field, scope: [ :activerecord, :helper_popover, object.class.name.underscore.to_sym ], default: nil)
29
+ object_name = object.class.name.underscore.to_sym
30
+ I18n.t(attribute, scope: [ :activerecord, :helper_popover, object_name ], default: nil)
30
31
  end
31
32
 
32
- def label_value(label, object, field, id)
33
- return object.class.human_attribute_name(field) if label.nil? && !object.nil? && !field.nil?
33
+ def label_value(label, object, attribute, id)
34
+ return object.class.human_attribute_name(attribute) if label.nil? && !object.nil? && !attribute.nil?
34
35
  return id.to_s.humanize if label.nil? && !id.nil?
35
36
  return label unless label.nil?
36
37
 
@@ -52,20 +53,14 @@ class Fluxbit::Form::Component < Fluxbit::Component
52
53
  def errors
53
54
  return [] unless @object&.errors&.any?
54
55
 
55
- @object.errors.filter { |f| f.attribute == @field }.map(&:full_message)
56
+ @object.errors.filter { |f| f.attribute == @attribute }.map(&:full_message)
56
57
  end
57
58
 
58
- def helper_text
59
- return "" if @helper_text.blank?
59
+ def help_text
60
+ return "" if @help_text.blank? || @help_text.compact.blank?
60
61
 
61
- # safe_join(
62
- # @helper_text.compact.map do |text|
63
- # Fluxbit::HelperTextComponent.new(color: @color).with_content(text).render_in(view_context)
64
- # end
65
- # )
66
-
67
- @helper_text.compact.map do |text|
68
- concat Fluxbit::Form::HelperTextComponent.new(color: @color).with_content(text).render_in(view_context)
69
- end
62
+ @help_text.compact.map do |text|
63
+ Fluxbit::Form::HelpTextComponent.new(color: @color).with_content(text).render_in(view_context)
64
+ end.join.html_safe
70
65
  end
71
66
  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_props [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_props = @props.delete(:icon_props) || { 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: @icon, class: styles[:icon])
38
+ end
39
+ end
@@ -0,0 +1,26 @@
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
+
13
+ @object = @form&.object
14
+ @help_text = define_help_text(props.delete(:help_text), @object, @attribute)
15
+ @helper_popover = define_helper_popover(props.delete(:helper_popover), @object, @attribute)
16
+ @helper_popover_placement = props.delete(:helper_popover_placement) || "right"
17
+ @label = label_value(props.delete(:label), @object, @attribute, @id)
18
+ @wrapper_html = props.delete(:wrapper_html) || {}
19
+ define_wrapper_options
20
+ end
21
+
22
+ def define_wrapper_options
23
+ add(to: @wrapper_html, class: "required") if @props[:required].present?
24
+ add(to: @wrapper_html, class: @name) if @name.present?
25
+ end
26
+ end
@@ -79,6 +79,7 @@ class Fluxbit::Form::FormBuilderComponent < Fluxbit::Component
79
79
  else
80
80
  ''
81
81
  end}#{element_type(kwargs[:type])}Component".constantize
82
+
82
83
  unless kwargs[:with_content]
83
84
  return content_tag(:div, render(component_klass.new(**kwargs), &block), class: outer_div)
84
85
  end
@@ -88,7 +89,7 @@ class Fluxbit::Form::FormBuilderComponent < Fluxbit::Component
88
89
  end
89
90
 
90
91
  def element_type(type)
91
- return "TextInput" if type.nil? || type.to_s.in?(TEXT_TYPES)
92
+ return "TextField" if type.nil? || type.to_s.in?(TEXT_TYPES)
92
93
  return type.to_s.concat("_input").camelcase if type.to_s.in?(INPUT_TYPES)
93
94
 
94
95
  case type
@@ -104,14 +105,11 @@ class Fluxbit::Form::FormBuilderComponent < Fluxbit::Component
104
105
  def generate_elements
105
106
  return elements if elements?
106
107
 
107
- safe_join(*@elements.map { |element| choose_element(element.merge({ form: @form }), nil) })
108
- end
109
-
110
- def generate_div
111
- safe_join errors?, content_tag(:div, generate_elements, @props)
108
+ safe_join(@elements.map { |element| choose_element(element.merge({ form: @form }), nil) })
112
109
  end
113
110
 
114
111
  def call
115
- generate_div
112
+ elements_rendered = elements? ? elements : safe_join(@elements.map { |element| choose_element(element.merge({ form: @form }), nil) })
113
+ safe_join [ errors?, content_tag(:div, elements_rendered, @props) ]
116
114
  end
117
115
  end
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # The `Fluxbit::HelperTextComponent` is a component for rendering customizable helper text elements.
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
- class Fluxbit::Form::HelperTextComponent < Fluxbit::Form::Component
8
- include Fluxbit::Config::Form::HelperTextComponent
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
  #
@@ -1,36 +1,39 @@
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
- cattr_accessor :styles do
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
- def initialize(color: :default, form: nil, with_content: nil, helper_text: nil,
24
- sizing: :sm, helper_popover: nil, helper_popover_placement: "right", **props)
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, :failure, :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
- @sizing = sizing.in?(styles[:sizes].keys) ? sizing : :sm
28
- @with_content = with_content
29
- @helper_text = helper_text.is_a?(Array) ? helper_text : [ helper_text ]
30
- @helper_popover = helper_popover
31
- @helper_popover_placement = helper_popover_placement
32
- color = :default unless color.in? %i[info default success failure warning]
33
- add class: styles[:colors][color], to: @props, first_element: true
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
+
36
+ add class: styles[:colors][@color], to: @props, first_element: true
34
37
  add class: styles[:base], to: @props, first_element: true
35
38
  add class: styles[:sizes][@sizing], to: @props, first_element: true
36
39
  end
@@ -39,7 +42,7 @@ class Fluxbit::Form::LabelComponent < Fluxbit::Form::Component
39
42
  return "" if @helper_popover.nil?
40
43
 
41
44
  content_tag :span,
42
- anyicon(icon: "heroicons_solid:question-mark-circle", class: "w-4 h-4"),
45
+ anyicon(icon: @@helper_popover_icon, class: @@helper_popover_icon_class),
43
46
  {
44
47
  "data-popover-placement": @helper_popover_placement,
45
48
  "data-popover-target": target,
@@ -57,7 +60,7 @@ class Fluxbit::Form::LabelComponent < Fluxbit::Form::Component
57
60
  safe_join(
58
61
  [
59
62
  content_tag(:label, safe_join([ content || @with_content, span_helper_popover ]), @props),
60
- helper_text,
63
+ help_text,
61
64
  render_popover
62
65
  ]
63
66
  )
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Fluxbit::Form::RangeComponent` renders a styled range slider for selecting numeric values within a range.
4
+ # It supports vertical and horizontal orientation, sizing options, helper text, labels, and full compatibility with Rails form builders.
5
+ # Custom classes and HTML attributes can be passed for further styling and control.
6
+ #
7
+ # @example Basic usage
8
+ # = render Fluxbit::Form::RangeComponent.new(name: :volume, label: "Volume")
9
+ #
10
+ # @see docs/03_Forms/Range.md For detailed documentation and examples.
11
+ class Fluxbit::Form::RangeComponent < Fluxbit::Form::FieldComponent
12
+ include Fluxbit::Config::Form::RangeComponent
13
+
14
+ # Initializes the range component with the given properties.
15
+ #
16
+ # @param name [String] Name of the field (required unless using form builder)
17
+ # @param label [String] Label for the input (optional)
18
+ # @param value [Numeric] Value for the range input (optional)
19
+ # @param min [Numeric] Minimum value for the range slider (optional)
20
+ # @param max [Numeric] Maximum value for the range slider (optional)
21
+ # @param step [Numeric] Step value for the slider (optional)
22
+ # @param vertical [Boolean] Renders the slider vertically if true (default: false)
23
+ # @param sizing [Integer] Size index for slider height/thickness (default: config default)
24
+ # @param help_text [String] Helper or error text below the field
25
+ # @param class [String] Additional CSS classes for the input element
26
+ # @param ... any other HTML attribute supported by the <input type="range"> tag
27
+ def initialize(**props)
28
+ super(**props)
29
+ @vertical = options(@props.delete(:vertical), collection: [ true, false ], default: @@vertical)
30
+ @sizing = @props[:sizing].to_i || @@sizing
31
+ @sizing = (styles[:sizes].count - 1) if @sizing > (styles[:sizes].count - 1)
32
+ @props[:type] = "range"
33
+ @props[:style] = @props[:style] || "" + ";transform: rotate(270deg);" if @vertical
34
+
35
+ add(class: styles[:sizes][@sizing], to: @props, first_element: true)
36
+ add(class: styles[:base], to: @props, first_element: true)
37
+ end
38
+
39
+ def range
40
+ if @form.nil?
41
+ text_field_tag @name, @value, @props
42
+ else
43
+ @form.text_field(@attribute, **@props)
44
+ end
45
+ end
46
+
47
+ def call
48
+ content_tag :div, **@wrapper_html do
49
+ safe_join [ label, range, help_text ]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The `Fluxbit::Form::SelectComponent` is a styled dropdown/select field for forms.
4
+ # It supports standard, grouped, and time zone options, integrates with Rails form builders,
5
+ # and provides flexible props for prompt, disabled/selected options, helper text, and more.
6
+ #
7
+ # @example Basic usage
8
+ # = render Fluxbit::Form::SelectComponent.new(name: :role, options: ["Admin", "User", "Guest"], label: "User Role")
9
+ #
10
+ # @see docs/03_Forms/Select.md For detailed documentation and examples.
11
+ class Fluxbit::Form::SelectComponent < Fluxbit::Form::TextFieldComponent
12
+ # Initializes the select component with the given properties.
13
+ #
14
+ # @param name [String] Name of the field (required unless using form builder)
15
+ # @param label [String] Label for the input (optional)
16
+ # @param value [String] Value for the field (optional)
17
+ # @param grouped [Boolean] Enables grouped select options (default: false)
18
+ # @param time_zone [Boolean] Uses Rails time zone select options (default: false)
19
+ # @param select_options [Hash] Options for select tag (prompt, selected, disabled, etc)
20
+ # @param choices [Array] List of choices for options (alternative to options)
21
+ # @param options [Array, Hash] List or hash of options (or groups if grouped)
22
+ # @param help_text [String] Helper or error text below the field
23
+ # @param class [String] Additional CSS classes for the select element
24
+ # @param ... any other HTML attribute supported by <select>
25
+ def initialize(**props)
26
+ super(**props)
27
+ @grouped = @props.delete(:grouped) || false
28
+ @time_zone = @props.delete(:time_zone) || false
29
+ @select_options = @props.delete(:select_options) || {}
30
+ @choices = @props.delete(:choices) || nil
31
+ @options = @props.delete(:options) || {}
32
+ @options = ::ActiveSupport::TimeZone.all if @time_zone
33
+ end
34
+
35
+ def input
36
+ if @form.present? && @attribute.present?
37
+ @form.select(
38
+ @attribute,
39
+ build_options_for_select,
40
+ @select_options,
41
+ @props
42
+ )
43
+ else
44
+ select_tag(
45
+ @name,
46
+ build_options_for_select,
47
+ @props
48
+ )
49
+ end
50
+ end
51
+
52
+ def build_options_for_select
53
+ if @grouped
54
+ grouped_options_for_select(
55
+ @options,
56
+ @selected,
57
+ disabled: @disabled_options,
58
+ prompt: @prompt,
59
+ divider: @divider
60
+ )
61
+ elsif @time_zone
62
+ time_zone_options_for_select(@selected)
63
+ else
64
+ options_for_select(@options, selected: @selected, disabled: @disabled_options)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def grouped_selected_option
71
+ @options.each do |group|
72
+ group_to_traverse = @divider ? group[1] : group
73
+ if group_to_traverse.is_a?(String)
74
+ return group_to_traverse if group_to_traverse == @selected.to_s
75
+
76
+ next
77
+ end
78
+
79
+ group_to_traverse.each do |item|
80
+ if item.is_a?(Array) && item[1] == @selected.to_s
81
+ return item[0]
82
+ elsif item.is_a?(String) && item == @selected.to_s
83
+ return item
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end