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
|
@@ -30,7 +30,13 @@ class Fluxbit::CardComponent < Fluxbit::Component
|
|
|
30
30
|
|
|
31
31
|
renders_one :header
|
|
32
32
|
renders_one :footer
|
|
33
|
-
|
|
33
|
+
renders_many :sections, lambda { |*args, **props, &block|
|
|
34
|
+
no_class = props.delete(:no_class) || false
|
|
35
|
+
add(class: styles[:body], to: props) unless no_class
|
|
36
|
+
# remove_class_from_props props
|
|
37
|
+
|
|
38
|
+
tag.div(block.call, **props)
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
# Initializes the card component with various customization options.
|
|
36
42
|
#
|
|
@@ -50,7 +56,7 @@ class Fluxbit::CardComponent < Fluxbit::Component
|
|
|
50
56
|
# @param popover_trigger [String] Trigger event for the popover.
|
|
51
57
|
# @param props [Hash] Additional HTML attributes for the container.
|
|
52
58
|
def initialize(color: :default, shadow: true, border: true, rounded: true, hoverable: false,
|
|
53
|
-
image: nil, image_position: :top,
|
|
59
|
+
image: nil, image_position: :top, image_html: {},
|
|
54
60
|
tooltip_text: nil, tooltip_placement: "top", tooltip_trigger: "hover",
|
|
55
61
|
popover_text: nil, popover_placement: "top", popover_trigger: "click",
|
|
56
62
|
**props)
|
|
@@ -61,7 +67,7 @@ class Fluxbit::CardComponent < Fluxbit::Component
|
|
|
61
67
|
@hoverable = hoverable
|
|
62
68
|
@image = image
|
|
63
69
|
@image_position = image_position.to_sym
|
|
64
|
-
@
|
|
70
|
+
@image_html = image_html
|
|
65
71
|
@tooltip_text = tooltip_text
|
|
66
72
|
@tooltip_placement = tooltip_placement
|
|
67
73
|
@tooltip_trigger = tooltip_trigger
|
|
@@ -69,7 +75,7 @@ class Fluxbit::CardComponent < Fluxbit::Component
|
|
|
69
75
|
@popover_placement = popover_placement
|
|
70
76
|
@popover_trigger = popover_trigger
|
|
71
77
|
@props = props
|
|
72
|
-
@
|
|
78
|
+
@image_html[:src] = @image
|
|
73
79
|
end
|
|
74
80
|
|
|
75
81
|
def before_render
|
|
@@ -89,45 +95,42 @@ class Fluxbit::CardComponent < Fluxbit::Component
|
|
|
89
95
|
def call
|
|
90
96
|
container_tag = @props[:href] ? :a : :div
|
|
91
97
|
|
|
92
|
-
header_html = header ?
|
|
93
|
-
footer_html = footer ?
|
|
94
|
-
body_content =
|
|
98
|
+
header_html = header? ? tag.div(header, class: self.class.styles[:header]) : nil
|
|
99
|
+
footer_html = footer? ? tag.div(footer, class: self.class.styles[:footer]) : nil
|
|
100
|
+
body_content = content || safe_join(sections)
|
|
95
101
|
|
|
96
102
|
if @image && @image_position == :top
|
|
97
103
|
# Top image layout: image at the top, then header, body, and footer.
|
|
98
|
-
add(class: styles[:image_top], to: @
|
|
99
|
-
image_html =
|
|
100
|
-
body_html = body_content ? content_tag(:div, body_content, class: self.class.styles[:body]) : nil
|
|
104
|
+
add(class: styles[:image_top], to: @image_html)
|
|
105
|
+
image_html = tag.img(**@image_html)
|
|
101
106
|
|
|
102
107
|
content_tag(container_tag, **@props) do
|
|
103
108
|
concat(image_html)
|
|
104
109
|
concat(header_html) if header_html
|
|
105
|
-
concat(
|
|
110
|
+
concat(body_content) if body_content
|
|
111
|
+
concat(content) if content?
|
|
106
112
|
concat(footer_html) if footer_html
|
|
107
113
|
end
|
|
108
114
|
elsif @image && @image_position == :left
|
|
109
115
|
# Left image layout: image on the left and content on the right in a flex container.
|
|
110
|
-
add(class: styles[:image_left], to: @
|
|
111
|
-
image_html =
|
|
112
|
-
|
|
113
|
-
end
|
|
114
|
-
content_inner = "".html_safe
|
|
115
|
-
content_inner << header_html.to_s if header_html
|
|
116
|
-
if body_content.present?
|
|
117
|
-
content_inner << content_tag(:div, body_content, class: self.class.styles[:body] + " " + self.class.styles[:content_left])
|
|
116
|
+
add(class: styles[:image_left], to: @image_html)
|
|
117
|
+
image_html = tag.div(class: "x") do
|
|
118
|
+
tag.img(**@image_html)
|
|
118
119
|
end
|
|
119
|
-
|
|
120
|
+
content_parts = []
|
|
121
|
+
content_parts << header_html if header_html
|
|
122
|
+
content_parts << body_content if body_content.present?
|
|
123
|
+
content_parts << footer_html if footer_html
|
|
120
124
|
|
|
121
125
|
content_tag(container_tag, **@props) do
|
|
122
126
|
concat(image_html)
|
|
123
|
-
concat(
|
|
127
|
+
concat(tag.div(safe_join(content_parts), class: "flex-1"))
|
|
124
128
|
end
|
|
125
129
|
else
|
|
126
130
|
# Fallback: render without image or with an unrecognized image_position.
|
|
127
|
-
body_html = body_content ? content_tag(:div, body_content, class: self.class.styles[:body]) : nil
|
|
128
131
|
content_tag(container_tag, **@props) do
|
|
129
132
|
concat(header_html) if header_html
|
|
130
|
-
concat(
|
|
133
|
+
concat(body_content) if body_content
|
|
131
134
|
concat(footer_html) if footer_html
|
|
132
135
|
end
|
|
133
136
|
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The `Fluxbit::CarouselComponent` is a customizable carousel component that extends `Fluxbit::Component`.
|
|
5
|
+
# It allows you to create image carousels or content sliders with various features like automatic sliding,
|
|
6
|
+
# indicators, navigation controls, and customizable animations.
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# = fx_carousel do |c|
|
|
10
|
+
# c.with_slide { image_tag("slide1.jpg") }
|
|
11
|
+
# c.with_slide { image_tag("slide2.jpg") }
|
|
12
|
+
# c.with_slide { image_tag("slide3.jpg") }
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @see docs/02_Components/Carousel.md For detailed documentation.
|
|
16
|
+
class Fluxbit::CarouselComponent < Fluxbit::Component
|
|
17
|
+
include Fluxbit::Config::CarouselComponent
|
|
18
|
+
|
|
19
|
+
renders_many :slides, lambda { |**props, &block|
|
|
20
|
+
begin
|
|
21
|
+
@slides_group << ComponentObj.new(props, view_context.capture(&block))
|
|
22
|
+
rescue
|
|
23
|
+
@slides_group << ComponentObj.new(props, nil)
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Initializes the carousel component with the given properties.
|
|
29
|
+
#
|
|
30
|
+
# @param [Hash] **props The properties to customize the component.
|
|
31
|
+
# @option props [Boolean] :slide (true) Enables automatic sliding between items.
|
|
32
|
+
# @option props [Integer] :slide_interval (3000) Interval in milliseconds between automatic slides.
|
|
33
|
+
# @option props [Boolean] :indicators (true) Shows slide indicator dots at the bottom.
|
|
34
|
+
# @option props [Boolean] :controls (true) Shows previous/next navigation controls.
|
|
35
|
+
# @option props [String] :left_control (nil) Custom content/text for left control button.
|
|
36
|
+
# @option props [String] :right_control (nil) Custom content/text for right control button.
|
|
37
|
+
# @option props [String] :remove_class ('') CSS classes to remove from the default class list.
|
|
38
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
39
|
+
#
|
|
40
|
+
# @return [Fluxbit::CarouselComponent]
|
|
41
|
+
def initialize(**props)
|
|
42
|
+
super
|
|
43
|
+
@props = props
|
|
44
|
+
@slides_group = []
|
|
45
|
+
|
|
46
|
+
@slide = options(@props.delete(:slide), default: @@slide)
|
|
47
|
+
@slide_interval = options(@props.delete(:slide_interval), default: @@slide_interval)
|
|
48
|
+
@indicators = options(@props.delete(:indicators), default: @@indicators)
|
|
49
|
+
@controls = options(@props.delete(:controls), default: @@controls)
|
|
50
|
+
@left_control = @props.delete(:left_control)
|
|
51
|
+
@right_control = @props.delete(:right_control)
|
|
52
|
+
|
|
53
|
+
declare_classes
|
|
54
|
+
|
|
55
|
+
# Handle class removal
|
|
56
|
+
@props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
|
|
57
|
+
|
|
58
|
+
# Flowbite carousel data attributes
|
|
59
|
+
@props[:data] ||= {}
|
|
60
|
+
@props[:data][:carousel] = @slide ? "slide" : "static"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call
|
|
64
|
+
slides # Ensure slides are rendered
|
|
65
|
+
|
|
66
|
+
tag.div(**@props) do
|
|
67
|
+
concat(render_slides_container)
|
|
68
|
+
concat(render_indicators) if @indicators
|
|
69
|
+
concat(render_controls) if @controls
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def declare_classes
|
|
76
|
+
add(class: styles[:base], to: @props, first_element: true)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def render_slides_container
|
|
80
|
+
tag.div(class: styles[:slides_container]) do
|
|
81
|
+
safe_join(@slides_group.map.with_index { |slide, index| render_slide(slide, index) })
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def render_slide(slide, index)
|
|
86
|
+
slide_props = slide.props.dup
|
|
87
|
+
add(class: styles[:slide][:base], to: slide_props, first_element: true)
|
|
88
|
+
add(class: styles[:slide][:inactive], to: slide_props) unless index.zero?
|
|
89
|
+
|
|
90
|
+
slide_props[:data] ||= {}
|
|
91
|
+
slide_props[:data][:carousel_item] = index.zero? ? "active" : true
|
|
92
|
+
|
|
93
|
+
tag.div(**slide_props) do
|
|
94
|
+
slide.content
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def render_indicators
|
|
99
|
+
tag.div(class: styles[:indicators][:container]) do
|
|
100
|
+
safe_join(@slides_group.count.times.map { |index| render_indicator(index) })
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def render_indicator(index)
|
|
105
|
+
button_props = {
|
|
106
|
+
type: "button",
|
|
107
|
+
class: styles[:indicators][:button],
|
|
108
|
+
"aria-current": index.zero?.to_s,
|
|
109
|
+
"aria-label": "Slide #{index + 1}",
|
|
110
|
+
data: {
|
|
111
|
+
carousel_slide_to: index
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
tag.button(**button_props)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def render_controls
|
|
119
|
+
safe_join([
|
|
120
|
+
render_control_button(:previous),
|
|
121
|
+
render_control_button(:next)
|
|
122
|
+
])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def render_control_button(direction)
|
|
126
|
+
is_previous = direction == :previous
|
|
127
|
+
custom_content = is_previous ? @left_control : @right_control
|
|
128
|
+
|
|
129
|
+
button_props = {
|
|
130
|
+
type: "button",
|
|
131
|
+
class: [styles[:controls][:button], is_previous ? styles[:controls][:previous] : styles[:controls][:next]].join(" "),
|
|
132
|
+
data: {}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Add Flowbite carousel control data attribute
|
|
136
|
+
if is_previous
|
|
137
|
+
button_props[:data][:carousel_prev] = ""
|
|
138
|
+
else
|
|
139
|
+
button_props[:data][:carousel_next] = ""
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
tag.button(**button_props) do
|
|
143
|
+
if custom_content
|
|
144
|
+
custom_content
|
|
145
|
+
else
|
|
146
|
+
concat(tag.span(class: styles[:controls][:icon_wrapper]) do
|
|
147
|
+
is_previous ? chevron_left(class: styles[:controls][:icon]) : chevron_right(class: styles[:controls][:icon])
|
|
148
|
+
end)
|
|
149
|
+
concat(tag.span("#{direction.to_s.capitalize} slide", class: styles[:controls][:sr_only]))
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require "anyicon"
|
|
4
4
|
|
|
5
5
|
class Fluxbit::Component < ViewComponent::Base
|
|
6
|
+
include Fluxbit::IconHelpers
|
|
7
|
+
|
|
6
8
|
# Custom class to hold button properties and content
|
|
7
9
|
ComponentObj = Data.define(:props, :content)
|
|
8
10
|
|
|
@@ -27,7 +29,14 @@ class Fluxbit::Component < ViewComponent::Base
|
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
def remove_class(elements, from)
|
|
30
|
-
|
|
32
|
+
return "" if from.blank?
|
|
33
|
+
return from if elements.blank?
|
|
34
|
+
|
|
35
|
+
from.split.reject { |c| c.in?((elements || "").split) }.join(" ")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def remove_class_from_props(props)
|
|
39
|
+
props[:class] = remove_class(props.delete(:remove_class) || "", props[:class])
|
|
31
40
|
end
|
|
32
41
|
|
|
33
42
|
def options(value, collection: nil, default: nil)
|
|
@@ -45,6 +54,14 @@ class Fluxbit::Component < ViewComponent::Base
|
|
|
45
54
|
]
|
|
46
55
|
end
|
|
47
56
|
|
|
57
|
+
def popover?
|
|
58
|
+
false # Override in components that support popovers via renders_one :popover
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tooltip?
|
|
62
|
+
false # Override in components that support tooltips via renders_one :tooltip
|
|
63
|
+
end
|
|
64
|
+
|
|
48
65
|
def add_popover_or_tooltip
|
|
49
66
|
if popover? || @popover_text.present?
|
|
50
67
|
@props["data-popover-placement"] = @popover_placement
|
|
@@ -66,8 +83,12 @@ class Fluxbit::Component < ViewComponent::Base
|
|
|
66
83
|
(0...10).map { ('a'..'z').to_a[rand(26)] }.join}_target"
|
|
67
84
|
end
|
|
68
85
|
|
|
69
|
-
def anyicon(icon
|
|
70
|
-
Anyicon::Icon.render(icon
|
|
86
|
+
def anyicon(icon, **props)
|
|
87
|
+
Anyicon::Icon.render(icon, **props)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def icon(icon_name, **props)
|
|
91
|
+
anyicon(icon_name, **props)
|
|
71
92
|
end
|
|
72
93
|
|
|
73
94
|
def random_id
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<%= tag.div(**@props) do %>
|
|
2
|
+
<% if @swipeable %>
|
|
3
|
+
<div class="p-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700" data-drawer-toggle="<%= @props[:id] %>">
|
|
4
|
+
<span class="absolute w-8 h-1 -translate-x-1/2 bg-gray-300 rounded-lg top-3 left-1/2 dark:bg-gray-600"></span>
|
|
5
|
+
<% end %>
|
|
6
|
+
<% if header? %>
|
|
7
|
+
<h5 id="<%= @props[:id] %>-label" class="inline-flex items-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400">
|
|
8
|
+
<%= header %>
|
|
9
|
+
</h5>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if @swipeable %></div><% end %>
|
|
12
|
+
|
|
13
|
+
<% if @show_close_button %>
|
|
14
|
+
<button type="button"
|
|
15
|
+
<% if @using_stimulus %>
|
|
16
|
+
data-action="click->fx-drawer#hide"
|
|
17
|
+
<% else %>
|
|
18
|
+
data-drawer-hide="<%= @props[:id] %>"
|
|
19
|
+
<% end %>
|
|
20
|
+
aria-controls="<%= @props[:id] %>"
|
|
21
|
+
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm size-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-gray-600 dark:hover:text-white"
|
|
22
|
+
>
|
|
23
|
+
<%= close_icon(class: "w-3 h-3") %>
|
|
24
|
+
<span class="sr-only"><%= t("fluxbit.drawer.dismiss") %></span>
|
|
25
|
+
</button>
|
|
26
|
+
<% end %>
|
|
27
|
+
<div>
|
|
28
|
+
<%= content %>
|
|
29
|
+
</div>
|
|
30
|
+
<% end %>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The `Fluxbit::DrawerComponent` is a customizable alert component that extends `Fluxbit::Component`.
|
|
5
|
+
# It provides various options to display alert messages with different styles, icons, and behaviors
|
|
6
|
+
# such as close functionality and animations.
|
|
7
|
+
#
|
|
8
|
+
# Example usage:
|
|
9
|
+
# = render Fluxbit::DrawerComponent.new(
|
|
10
|
+
# placement: :left,
|
|
11
|
+
# sizing: :md,
|
|
12
|
+
# show_close_button: true,
|
|
13
|
+
# swipeable: false,
|
|
14
|
+
# shadow: true
|
|
15
|
+
# ) do |drawer|
|
|
16
|
+
# drawer.with_header do |header|
|
|
17
|
+
# "Header Content"
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
class Fluxbit::DrawerComponent < Fluxbit::Component
|
|
22
|
+
include Fluxbit::Config::DrawerComponent
|
|
23
|
+
|
|
24
|
+
renders_one :header, lambda { |*args, **props, &block|
|
|
25
|
+
# add(class: props[:class], to: @caption_html) if props[:class]
|
|
26
|
+
# @caption_html = props.merge(@caption_html)
|
|
27
|
+
# remove_class_from_props @caption_html
|
|
28
|
+
|
|
29
|
+
block.call
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Initializes the table component with the given properties.
|
|
34
|
+
#
|
|
35
|
+
# @param [Hash] props The properties to customize the table.
|
|
36
|
+
# @option props [Boolean] :striped (false) Determines if the table rows should be striped.
|
|
37
|
+
# @option props [Boolean] :bordered (false) Determines if the table should have borders.
|
|
38
|
+
# @option props [Boolean] :hover (false) Determines if the table rows should highlight on hover.
|
|
39
|
+
# @option props [Boolean] :shadow (false) Determines if the table should have a shadow effect.
|
|
40
|
+
# @option props [Hash] :wrapper_html Additional HTML attributes for the wrapper div.
|
|
41
|
+
# @option props [Hash] :thead_html Additional HTML attributes for the table header.
|
|
42
|
+
# @option props [Hash] :tbody_html Additional HTML attributes for the table body.
|
|
43
|
+
# @option props [Hash] :tr_html Additional HTML attributes for the table rows.
|
|
44
|
+
# @option props [Hash] :cells_html Additional HTML attributes for the table cells.
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# = render Fluxbit::DrawerComponent.new(
|
|
48
|
+
# placement: :left,
|
|
49
|
+
# sizing: :md,
|
|
50
|
+
# show_close_button: true,
|
|
51
|
+
# swipeable: false,
|
|
52
|
+
# shadow: true
|
|
53
|
+
# ) do |drawer|
|
|
54
|
+
# drawer.with_header do |header|
|
|
55
|
+
# "Header Content"
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# @return [Fluxbit::DrawerComponent]
|
|
60
|
+
#
|
|
61
|
+
def initialize(**props)
|
|
62
|
+
super
|
|
63
|
+
@props = props
|
|
64
|
+
@placement = options(@props.delete(:placement), collection: styles[:placements].keys, default: @@placement).to_sym
|
|
65
|
+
@sizing = options(@props.delete(:sizing), collection: styles[:sizes][:horizontal].keys, default: @@sizing).to_sym
|
|
66
|
+
@show_close_button = options @props.delete(:show_close_button), default: @@show_close_button
|
|
67
|
+
@swipeable = options @props.delete(:swipeable), default: @@swipeable
|
|
68
|
+
@shadow = options @props.delete(:shadow), default: @@shadow
|
|
69
|
+
@backdrop = options @props.delete(:backdrop), default: @@backdrop
|
|
70
|
+
@auto_show = options @props.delete(:auto_show), default: @@auto_show
|
|
71
|
+
@body_scrolling = options @props.delete(:body_scrolling), default: @@body_scrolling
|
|
72
|
+
@edge_offset = @props.delete(:edge_offset)
|
|
73
|
+
@backdrop_classes = @props.delete(:backdrop_classes)
|
|
74
|
+
|
|
75
|
+
if @swipeable
|
|
76
|
+
@placement = :bottom
|
|
77
|
+
@show_close_button = false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@props[:id] ||= fx_id
|
|
81
|
+
@props[:tabindex] = "-1"
|
|
82
|
+
@props["aria-labelledby"] = "#{@props[:id]}-label"
|
|
83
|
+
|
|
84
|
+
@using_stimulus = @props["data-fx-drawer-target"].present? || @props["data-controller"].to_s.include?("fx-drawer") ||
|
|
85
|
+
(
|
|
86
|
+
@props[:data].present? && (
|
|
87
|
+
@props[:data][:"fx-drawer-target"].present? || (@props[:data][:controller] || "").to_s.include?("fx-drawer")
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if @using_stimulus
|
|
92
|
+
if @props["data-controller"].to_s.include?("fx-drawer") || @props[:data][:controller].to_s.include?("fx-drawer")
|
|
93
|
+
@props["data-fx-drawer-auto-show-value"] = @auto_show
|
|
94
|
+
@props["data-fx-drawer-placement-value"] = @placement.to_s
|
|
95
|
+
@props["data-fx-drawer-backdrop-value"] = @backdrop
|
|
96
|
+
@props["data-fx-drawer-body-scrolling-value"] = @body_scrolling
|
|
97
|
+
@props["data-fx-drawer-edge-value"] = @swipeable
|
|
98
|
+
@props["data-fx-drawer-edge-offset-value"] = @edge_offset if @edge_offset.present? && @swipeable
|
|
99
|
+
@props["data-fx-drawer-backdrop-classes-value"] = @backdrop_classes if @backdrop_classes.present?
|
|
100
|
+
else
|
|
101
|
+
@props["data-auto-show"] = @auto_show
|
|
102
|
+
@props["data-placement"] = @placement.to_s
|
|
103
|
+
@props["data-backdrop"] = @backdrop
|
|
104
|
+
@props["data-body-scrolling"] = @body_scrolling
|
|
105
|
+
@props["data-edge"] = @swipeable
|
|
106
|
+
@props["data-edge-offset"] = @edge_offset if @edge_offset.present? && @swipeable
|
|
107
|
+
@props["data-backdrop-classes"] = @backdrop_classes if @backdrop_classes.present?
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
add(
|
|
112
|
+
class: [ styles[:root],
|
|
113
|
+
styles[:placements][@placement],
|
|
114
|
+
styles[:color],
|
|
115
|
+
@swipeable ? styles[:swipeable][:default] : nil,
|
|
116
|
+
@shadow ? styles[:shadow] : nil,
|
|
117
|
+
size_class ],
|
|
118
|
+
to: @props
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def size_class
|
|
123
|
+
styles[:sizes][@placement.in?([ :left, :right ]) ? :horizontal : :vertical][@sizing]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::DropdownComponent` is a component for rendering customizable Dropdown containers.
|
|
4
|
+
class Fluxbit::DropdownComponent < Fluxbit::Component
|
|
5
|
+
include Fluxbit::Config::DropdownComponent
|
|
6
|
+
|
|
7
|
+
renders_many :items, Fluxbit::DropdownItemComponent
|
|
8
|
+
|
|
9
|
+
# Initializes the Dropdown component with various customization options.
|
|
10
|
+
#
|
|
11
|
+
# @param sizing [Integer] The alignment of items. Defaults to `0` (small).
|
|
12
|
+
# @param divider [Boolean] Whether the Dropdown has a division between the items. Defaults to `true`.
|
|
13
|
+
# @param props [Hash] Additional HTML attributes for the container.
|
|
14
|
+
def initialize(**props)
|
|
15
|
+
@props = props
|
|
16
|
+
@sizing = props.delete(:sizing) || @@sizing
|
|
17
|
+
@auto_divider = options props.delete(:auto_divider), default: @@auto_divider
|
|
18
|
+
@props[:id] ||= "dropdown-#{random_id}"
|
|
19
|
+
|
|
20
|
+
add to: @props, class: [
|
|
21
|
+
styles[:base],
|
|
22
|
+
styles[:sizes][@sizing],
|
|
23
|
+
@auto_divider ? styles[:auto_divider] : nil
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
remove_class_from_props(@props)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_item
|
|
30
|
+
@props[:id]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call
|
|
34
|
+
tag.div(**@props) do
|
|
35
|
+
tag.ul(class: styles[:ul]) do
|
|
36
|
+
concat(safe_join(items)) if items.any?
|
|
37
|
+
concat(Fluxbit::DropdownItemComponent.new(content).render_in(view_context)) if content.present?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::DropdownItemComponent` is a component for rendering customizable Dropdown item containers.
|
|
4
|
+
class Fluxbit::DropdownItemComponent < Fluxbit::Component
|
|
5
|
+
include Fluxbit::Config::DropdownComponent
|
|
6
|
+
|
|
7
|
+
renders_many :items, Fluxbit::DropdownItemComponent
|
|
8
|
+
|
|
9
|
+
def initialize(param_content = "", **props)
|
|
10
|
+
@param_content = param_content
|
|
11
|
+
@item_props = props
|
|
12
|
+
@type = @item_props.delete(:as) || :div
|
|
13
|
+
@height = @item_props.delete(:height) || @@height
|
|
14
|
+
@icon = @item_props.delete(:icon)
|
|
15
|
+
add to: @item_props, class: styles[:items][:heights][@height]
|
|
16
|
+
remove_class_from_props(@item_props)
|
|
17
|
+
|
|
18
|
+
@content_html = @item_props.delete(:content_html) || {}
|
|
19
|
+
|
|
20
|
+
@icon_html = @item_props.delete(:icon_html) || {}
|
|
21
|
+
add to: @icon_html, class: styles[:icon] if @icon.present?
|
|
22
|
+
|
|
23
|
+
@divider = @item_props.delete(:divider) || false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def call
|
|
27
|
+
return tag.li(class: styles[:divider]) if @divider
|
|
28
|
+
|
|
29
|
+
if items.any?
|
|
30
|
+
@content_html["data-dropdown-toggle"] = @item_props.delete(:dropdown_id) || "inner-dropdown-#{random_id}"
|
|
31
|
+
@content_html["data-dropdown-placement"] = @item_props.delete(:dropdown_placement) || "right-start"
|
|
32
|
+
height = @item_props.delete(:sizing) || 0
|
|
33
|
+
auto_divider = @item_props.delete(:auto_divider) || true
|
|
34
|
+
@type = :button
|
|
35
|
+
@content_html[:type] = "button"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
add to: @content_html, class: styles[:items][:types][@type]
|
|
39
|
+
remove_class_from_props(@content_html)
|
|
40
|
+
|
|
41
|
+
tag.li(**@item_props) do
|
|
42
|
+
concat(content_tag(@type.to_sym, **@content_html) do
|
|
43
|
+
concat(tag.div(class: "flex") do
|
|
44
|
+
concat(anyicon(@icon, **@icon_html)) if @icon.present?
|
|
45
|
+
concat(content || @param_content)
|
|
46
|
+
end)
|
|
47
|
+
concat(chevron_right(class: "ms-3 ml-auto")) if items.any?
|
|
48
|
+
end)
|
|
49
|
+
concat(
|
|
50
|
+
inner_dropdown(items, id: @content_html["data-dropdown-toggle"], sizing: height, auto_divider: auto_divider)
|
|
51
|
+
) if items.any?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def inner_dropdown(items, id: "a", sizing: 0, auto_divider: true)
|
|
58
|
+
props = { id: id, class: styles[:base] }
|
|
59
|
+
add to: props, class: [
|
|
60
|
+
styles[:sizes][sizing],
|
|
61
|
+
auto_divider ? styles[:auto_divider] : nil
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
tag.div(**props) do
|
|
65
|
+
tag.ul(safe_join(items), class: styles[:ul])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -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
|