fluxbit_view_components 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/component.rb +15 -8
- data/app/components/fluxbit/form/dropzone_component.rb +3 -3
- data/app/components/fluxbit/form/field_component.rb +4 -2
- data/app/components/fluxbit/form/help_text_component.rb +1 -1
- data/app/components/fluxbit/form/label_component.rb +10 -3
- 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/select_component.rb +108 -11
- data/app/components/fluxbit/form/text_field_component.rb +40 -23
- data/app/components/fluxbit/form/toggle_component.rb +2 -2
- data/app/components/fluxbit/form/upload_image_component.html.erb +3 -3
- data/app/components/fluxbit/form/upload_image_component.rb +12 -1
- 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 +74 -4
- data/app/helpers/fluxbit/form_builder.rb +64 -15
- 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 +1 -1
- data/lib/fluxbit/config/form/dropzone_component.rb +1 -1
- data/lib/fluxbit/config/form/help_text_component.rb +1 -1
- data/lib/fluxbit/config/form/label_component.rb +3 -2
- 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/text_field_component.rb +11 -11
- data/lib/fluxbit/config/form/toggle_component.rb +5 -5
- 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 +20 -0
- 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 +58 -0
- metadata +107 -18
- 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
|
|
@@ -15,7 +15,7 @@ class Fluxbit::Form::Component < Fluxbit::Component
|
|
|
15
15
|
if help_text.nil? && !object.nil? && !attribute.nil?
|
|
16
16
|
help_text = I18n.t(
|
|
17
17
|
attribute,
|
|
18
|
-
scope: [
|
|
18
|
+
scope: [ object.class.name.pluralize.underscore.to_sym, :help_text ],
|
|
19
19
|
default: nil
|
|
20
20
|
)
|
|
21
21
|
end
|
|
@@ -24,14 +24,18 @@ class Fluxbit::Form::Component < Fluxbit::Component
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def define_helper_popover(helper_popover, object, attribute)
|
|
27
|
-
return
|
|
27
|
+
return nil if helper_popover == false
|
|
28
|
+
return helper_popover if !helper_popover.nil? || object.nil?
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
I18n.t(attribute, 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
33
|
def label_value(label, object, attribute, id)
|
|
34
|
-
|
|
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,7 +50,8 @@ 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
|
|
|
@@ -59,8 +64,10 @@ class Fluxbit::Form::Component < Fluxbit::Component
|
|
|
59
64
|
def help_text
|
|
60
65
|
return "" if @help_text.blank? || @help_text.compact.blank?
|
|
61
66
|
|
|
62
|
-
@help_text.compact.map do |text|
|
|
67
|
+
nodes = @help_text.compact.map do |text|
|
|
63
68
|
Fluxbit::Form::HelpTextComponent.new(color: @color).with_content(text).render_in(view_context)
|
|
64
|
-
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
view_context.safe_join(nodes)
|
|
65
72
|
end
|
|
66
73
|
end
|
|
@@ -19,7 +19,7 @@ class Fluxbit::Form::DropzoneComponent < Fluxbit::Form::FieldComponent
|
|
|
19
19
|
# @param title [Boolean, String] Title text above the dropzone (true for default, false to hide, or custom string)
|
|
20
20
|
# @param subtitle [Boolean, String] Subtitle text below the title (true for default, false to hide, or custom string)
|
|
21
21
|
# @param icon [String, Symbol] Icon to display above the title (defaults to config)
|
|
22
|
-
# @param
|
|
22
|
+
# @param icon_html [Hash] Extra props for the icon element
|
|
23
23
|
# @param height [Integer] Height preset (0: auto, 1: h-32, 2: h-64, 3: h-96; default is 0)
|
|
24
24
|
# @param help_text [String] Helper or error text below the field
|
|
25
25
|
# @param ... any other HTML attribute supported by file_field_tag
|
|
@@ -28,12 +28,12 @@ class Fluxbit::Form::DropzoneComponent < Fluxbit::Form::FieldComponent
|
|
|
28
28
|
@title = options(@props.delete(:title), default: true)
|
|
29
29
|
@subtitle = options(@props.delete(:subtitle), default: true)
|
|
30
30
|
@icon = @props.delete(:icon) || @@icon
|
|
31
|
-
@
|
|
31
|
+
@icon_html = @props.delete(:icon_html) || { class: styles[:icon] }
|
|
32
32
|
@height = @props.delete(:height) || @@height
|
|
33
33
|
add to: @props, class: "hidden"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def create_icon
|
|
37
|
-
anyicon(
|
|
37
|
+
anyicon(@icon, class: styles[:icon])
|
|
38
38
|
end
|
|
39
39
|
end
|