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
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The `Fluxbit::BottomNavigationComponent` is a customizable bottom navigation component that extends `Fluxbit::Component`.
|
|
5
|
+
# It allows you to create fixed bottom navigation bars with multiple menu items, icons, and text labels.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# = fx_bottom_navigation do |nav|
|
|
9
|
+
# nav.with_item(href: "/", icon: "heroicons_solid:home") { "Home" }
|
|
10
|
+
# nav.with_item(href: "/search", icon: "heroicons_solid:magnifying-glass") { "Search" }
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# @example With button group
|
|
14
|
+
# = fx_bottom_navigation do |nav|
|
|
15
|
+
# nav.with_button_group(columns: 3) do |group|
|
|
16
|
+
# group.with_button(active: true) { "New" }
|
|
17
|
+
# group.with_button { "Popular" }
|
|
18
|
+
# group.with_button { "Following" }
|
|
19
|
+
# end
|
|
20
|
+
# nav.with_item(href: "/", icon: "heroicons_solid:home") { "Home" }
|
|
21
|
+
# nav.with_item(href: "/wallet", icon: "heroicons_solid:wallet") { "Wallet" }
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# @see docs/02_Components/BottomNavigation.md For detailed documentation.
|
|
25
|
+
class Fluxbit::BottomNavigationComponent < Fluxbit::Component
|
|
26
|
+
include Fluxbit::Config::BottomNavigationComponent
|
|
27
|
+
|
|
28
|
+
renders_many :items, lambda { |**props, &block|
|
|
29
|
+
Item.new(**props, parent_config: self, parent_variant: @variant, &block)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
renders_one :cta, lambda { |**props, &block|
|
|
33
|
+
CTA.new(**props, parent_config: self, &block)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
renders_one :pagination, lambda { |**props, &block|
|
|
37
|
+
Pagination.new(**props, parent_config: self, &block)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
renders_one :button_group, lambda { |**props, &block|
|
|
41
|
+
ButtonGroup.new(**props, parent_config: self, &block)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Initializes the bottom navigation component with the given properties.
|
|
46
|
+
#
|
|
47
|
+
# @param [Hash] **props The properties to customize the component.
|
|
48
|
+
# @option props [Symbol] :variant (:default) The style variant (:default or :app_bar).
|
|
49
|
+
# @option props [Boolean] :border (true) Shows border at the top of the navigation.
|
|
50
|
+
# @option props [String] :remove_class ('') CSS classes to remove from the default class list.
|
|
51
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
52
|
+
#
|
|
53
|
+
# @return [Fluxbit::BottomNavigationComponent]
|
|
54
|
+
def initialize(**props)
|
|
55
|
+
super
|
|
56
|
+
@props = props
|
|
57
|
+
|
|
58
|
+
@variant = options(@props.delete(:variant), collection: styles[:variants].keys, default: @@variant)
|
|
59
|
+
@border = options(@props.delete(:border), default: @@border)
|
|
60
|
+
@remove_class = @props.delete(:remove_class) || ""
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call
|
|
64
|
+
items # Ensure items are rendered
|
|
65
|
+
cta # Ensure CTA is rendered if present
|
|
66
|
+
pagination # Ensure pagination is rendered if present
|
|
67
|
+
button_group # Ensure button_group is rendered if present
|
|
68
|
+
|
|
69
|
+
# Declare classes after button_group is rendered so we know which variant to use
|
|
70
|
+
declare_classes
|
|
71
|
+
|
|
72
|
+
# Handle class removal after classes are declared
|
|
73
|
+
@props[:class] = remove_class(@remove_class, @props[:class])
|
|
74
|
+
|
|
75
|
+
tag.div(**@props) do
|
|
76
|
+
safe_join([
|
|
77
|
+
(button_group if button_group?),
|
|
78
|
+
tag.div(class: container_classes) do
|
|
79
|
+
if cta?
|
|
80
|
+
# Insert CTA in the middle for both variants
|
|
81
|
+
half = (items.size / 2.0).floor
|
|
82
|
+
safe_join(items[0...half] + [tag.div(cta, class: styles[:cta_wrapper])] + items[half..])
|
|
83
|
+
elsif pagination?
|
|
84
|
+
# Insert pagination in the middle
|
|
85
|
+
half = (items.size / 2.0).floor
|
|
86
|
+
safe_join(items[0...half] + [pagination] + items[half..])
|
|
87
|
+
else
|
|
88
|
+
safe_join(items)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
].compact)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def declare_classes
|
|
98
|
+
base_key = button_group? ? :base_with_button_group : :base
|
|
99
|
+
add(class: styles[:variants][@variant][base_key], to: @props, first_element: true)
|
|
100
|
+
add(class: styles[:variants][@variant][:border], to: @props) if @border && @variant == :default
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def container_classes
|
|
104
|
+
base_key = button_group? ? :base_with_button_group : :base
|
|
105
|
+
base = styles[:container][base_key]
|
|
106
|
+
|
|
107
|
+
# Auto-calculate columns based on items count and presence of CTA/pagination
|
|
108
|
+
columns = calculate_columns
|
|
109
|
+
|
|
110
|
+
[
|
|
111
|
+
base,
|
|
112
|
+
styles[:container][:columns][columns - 2]
|
|
113
|
+
].compact.join(" ")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def calculate_columns
|
|
117
|
+
# Count items + additional columns for CTA/pagination
|
|
118
|
+
total = items.size
|
|
119
|
+
total += 1 if cta? # CTA occupies 1 grid cell
|
|
120
|
+
total += 2 if pagination? # Pagination spans 2 grid cells (col-span-2)
|
|
121
|
+
|
|
122
|
+
# Ensure columns is within valid range (2-6)
|
|
123
|
+
[[total, 2].max, 6].min
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Item component for bottom navigation
|
|
128
|
+
class Item < Fluxbit::Component
|
|
129
|
+
include Fluxbit::Config::BottomNavigationComponent
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Initializes the item component.
|
|
133
|
+
#
|
|
134
|
+
# @param [Hash] **props The properties to customize the item.
|
|
135
|
+
# @option props [String] :href The URL the item links to.
|
|
136
|
+
# @option props [String] :icon The icon to display.
|
|
137
|
+
# @option props [Boolean] :active (false) Whether the item is currently active.
|
|
138
|
+
# @option props [String] :tooltip_text Tooltip text to display on hover.
|
|
139
|
+
# @option props [Boolean] :sr_only (false) Show text for screen readers only.
|
|
140
|
+
# @option props [Fluxbit::BottomNavigationComponent] :parent_config Parent component configuration.
|
|
141
|
+
# @option props [Symbol] :parent_variant Parent component variant.
|
|
142
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
143
|
+
#
|
|
144
|
+
# @return [Item]
|
|
145
|
+
def initialize(**props, &block)
|
|
146
|
+
super(**props, &block)
|
|
147
|
+
@props = props
|
|
148
|
+
@parent_config = @props.delete(:parent_config)
|
|
149
|
+
@parent_variant = @props.delete(:parent_variant) || :default
|
|
150
|
+
|
|
151
|
+
@href = @props.delete(:href) || "#"
|
|
152
|
+
@icon = @props.delete(:icon)
|
|
153
|
+
@active = options(@props.delete(:active), default: false)
|
|
154
|
+
@tooltip_text = @props.delete(:tooltip_text)
|
|
155
|
+
@sr_only = options(@props.delete(:sr_only), default: @parent_variant == :app_bar)
|
|
156
|
+
|
|
157
|
+
add(class: styles[:item][:base], to: @props, first_element: true)
|
|
158
|
+
add(class: styles[:item][:active], to: @props) if @active
|
|
159
|
+
add(class: styles[:item][:inactive], to: @props) unless @active
|
|
160
|
+
|
|
161
|
+
@props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def call
|
|
165
|
+
setup_data_attributes
|
|
166
|
+
|
|
167
|
+
button_content = tag.button(type: "button", **@props) do
|
|
168
|
+
concat(render_icon) if @icon
|
|
169
|
+
concat(tag.span(content, class: text_classes))
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if @tooltip_text
|
|
173
|
+
safe_join([button_content, render_tooltip])
|
|
174
|
+
else
|
|
175
|
+
button_content
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def render_icon
|
|
182
|
+
return "" if @icon.blank?
|
|
183
|
+
|
|
184
|
+
tag.div(class: styles[:item][:icon_wrapper]) do
|
|
185
|
+
anyicon(@icon, class: styles[:item][:icon])
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def text_classes
|
|
190
|
+
@sr_only ? styles[:item][:sr_only] : styles[:item][:text]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def setup_data_attributes
|
|
194
|
+
@props[:data] ||= {}
|
|
195
|
+
@props[:data][:href] = @href
|
|
196
|
+
|
|
197
|
+
if @tooltip_text
|
|
198
|
+
tooltip_id = "tooltip-#{content.to_s.parameterize}"
|
|
199
|
+
@props[:data][:"tooltip-target"] = tooltip_id
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def render_tooltip
|
|
204
|
+
tooltip_id = "tooltip-#{content.to_s.parameterize}"
|
|
205
|
+
|
|
206
|
+
tag.div(id: tooltip_id, role: "tooltip", class: styles[:tooltip][:base]) do
|
|
207
|
+
concat(@tooltip_text)
|
|
208
|
+
concat(tag.div(class: styles[:tooltip][:arrow], data: { tooltip_arrow: "" }))
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
##
|
|
214
|
+
# CTA component for app bar variant
|
|
215
|
+
class CTA < Fluxbit::Component
|
|
216
|
+
include Fluxbit::Config::BottomNavigationComponent
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# Initializes the CTA component.
|
|
220
|
+
#
|
|
221
|
+
# @param [Hash] **props The properties to customize the CTA.
|
|
222
|
+
# @option props [String] :href The URL the CTA links to.
|
|
223
|
+
# @option props [String] :icon The icon to display.
|
|
224
|
+
# @option props [String] :tooltip_text Tooltip text to display on hover.
|
|
225
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
226
|
+
#
|
|
227
|
+
# @return [CTA]
|
|
228
|
+
def initialize(**props, &block)
|
|
229
|
+
super(**props, &block)
|
|
230
|
+
@props = props
|
|
231
|
+
@parent_config = @props.delete(:parent_config)
|
|
232
|
+
|
|
233
|
+
@href = @props.delete(:href) || "#"
|
|
234
|
+
@icon = @props.delete(:icon)
|
|
235
|
+
@tooltip_text = @props.delete(:tooltip_text)
|
|
236
|
+
|
|
237
|
+
add(class: styles[:cta][:button], to: @props, first_element: true)
|
|
238
|
+
|
|
239
|
+
@props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def call
|
|
243
|
+
setup_data_attributes
|
|
244
|
+
|
|
245
|
+
button_content = tag.button(type: "button", **@props) do
|
|
246
|
+
safe_join([
|
|
247
|
+
(@icon ? anyicon(@icon, class: styles[:cta][:icon]) : nil),
|
|
248
|
+
tag.span(content, class: styles[:item][:sr_only])
|
|
249
|
+
].compact)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
if @tooltip_text
|
|
253
|
+
safe_join([button_content, render_tooltip])
|
|
254
|
+
else
|
|
255
|
+
button_content
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
|
|
261
|
+
def setup_data_attributes
|
|
262
|
+
@props[:data] ||= {}
|
|
263
|
+
@props[:data][:href] = @href
|
|
264
|
+
|
|
265
|
+
if @tooltip_text
|
|
266
|
+
tooltip_id = "tooltip-#{content.to_s.parameterize}"
|
|
267
|
+
@props[:data][:"tooltip-target"] = tooltip_id
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def render_tooltip
|
|
272
|
+
tooltip_id = "tooltip-#{content.to_s.parameterize}"
|
|
273
|
+
|
|
274
|
+
tag.div(id: tooltip_id, role: "tooltip", class: styles[:tooltip][:base]) do
|
|
275
|
+
concat(@tooltip_text)
|
|
276
|
+
concat(tag.div(class: styles[:tooltip][:arrow], data: { tooltip_arrow: "" }))
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
##
|
|
282
|
+
# Pagination component for bottom navigation
|
|
283
|
+
class Pagination < Fluxbit::Component
|
|
284
|
+
include Fluxbit::Config::BottomNavigationComponent
|
|
285
|
+
|
|
286
|
+
##
|
|
287
|
+
# Initializes the pagination component.
|
|
288
|
+
#
|
|
289
|
+
# @param [Hash] **props The properties to customize the pagination.
|
|
290
|
+
# @option props [Integer] :current_page (1) The current page number.
|
|
291
|
+
# @option props [Integer] :total_pages (1) The total number of pages.
|
|
292
|
+
# @option props [String] :previous_href The URL for the previous page.
|
|
293
|
+
# @option props [String] :next_href The URL for the next page.
|
|
294
|
+
# @option props [String] :previous_label ("Previous") The label for the previous button.
|
|
295
|
+
# @option props [String] :next_label ("Next") The label for the next button.
|
|
296
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
297
|
+
#
|
|
298
|
+
# @return [Pagination]
|
|
299
|
+
def initialize(**props, &block)
|
|
300
|
+
super(**props, &block)
|
|
301
|
+
@props = props
|
|
302
|
+
@parent_config = @props.delete(:parent_config)
|
|
303
|
+
|
|
304
|
+
@current_page = @props.delete(:current_page) || 1
|
|
305
|
+
@total_pages = @props.delete(:total_pages) || 1
|
|
306
|
+
@previous_href = @props.delete(:previous_href) || "#"
|
|
307
|
+
@next_href = @props.delete(:next_href) || "#"
|
|
308
|
+
@previous_label = @props.delete(:previous_label) || "Previous"
|
|
309
|
+
@next_label = @props.delete(:next_label) || "Next"
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def call
|
|
313
|
+
tag.div(class: styles[:pagination][:container]) do
|
|
314
|
+
safe_join([
|
|
315
|
+
render_previous_button,
|
|
316
|
+
render_page_info,
|
|
317
|
+
render_next_button
|
|
318
|
+
])
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
private
|
|
323
|
+
|
|
324
|
+
def render_previous_button
|
|
325
|
+
tag.button(
|
|
326
|
+
type: "button",
|
|
327
|
+
class: styles[:pagination][:button],
|
|
328
|
+
data: { href: @previous_href },
|
|
329
|
+
disabled: @current_page <= 1
|
|
330
|
+
) do
|
|
331
|
+
concat(chevron_left(class: styles[:pagination][:icon]))
|
|
332
|
+
concat(tag.span(@previous_label, class: styles[:pagination][:sr_only]))
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def render_next_button
|
|
337
|
+
tag.button(
|
|
338
|
+
type: "button",
|
|
339
|
+
class: styles[:pagination][:button],
|
|
340
|
+
data: { href: @next_href },
|
|
341
|
+
disabled: @current_page >= @total_pages
|
|
342
|
+
) do
|
|
343
|
+
concat(tag.span(@next_label, class: styles[:pagination][:sr_only]))
|
|
344
|
+
concat(chevron_right(class: styles[:pagination][:icon]))
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def render_page_info
|
|
349
|
+
tag.div(class: styles[:pagination][:info]) do
|
|
350
|
+
"#{@current_page} of #{@total_pages}"
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
##
|
|
356
|
+
# ButtonGroup component for bottom navigation
|
|
357
|
+
class ButtonGroup < Fluxbit::Component
|
|
358
|
+
include Fluxbit::Config::BottomNavigationComponent
|
|
359
|
+
|
|
360
|
+
renders_many :buttons, lambda { |**props, &block|
|
|
361
|
+
Button.new(**props, parent_config: self, &block)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
##
|
|
365
|
+
# Initializes the button group component.
|
|
366
|
+
#
|
|
367
|
+
# @param [Hash] **props The properties to customize the button group.
|
|
368
|
+
# @option props [Integer] :columns (3) Number of columns for button grid (2-5).
|
|
369
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
370
|
+
#
|
|
371
|
+
# @return [ButtonGroup]
|
|
372
|
+
def initialize(**props, &block)
|
|
373
|
+
super(**props, &block)
|
|
374
|
+
@props = props
|
|
375
|
+
@parent_config = @props.delete(:parent_config)
|
|
376
|
+
@columns = @props.delete(:columns) || 3
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def call
|
|
380
|
+
buttons # Ensure buttons are rendered
|
|
381
|
+
|
|
382
|
+
tag.div(class: styles[:button_group][:container], role: "group") do
|
|
383
|
+
tag.div(class: button_group_grid_classes) do
|
|
384
|
+
safe_join(buttons)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
private
|
|
390
|
+
|
|
391
|
+
def button_group_grid_classes
|
|
392
|
+
[
|
|
393
|
+
styles[:button_group][:grid],
|
|
394
|
+
styles[:button_group][:columns][@columns - 2]
|
|
395
|
+
].compact.join(" ")
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
##
|
|
399
|
+
# Button component for button group
|
|
400
|
+
class Button < Fluxbit::Component
|
|
401
|
+
include Fluxbit::Config::BottomNavigationComponent
|
|
402
|
+
|
|
403
|
+
##
|
|
404
|
+
# Initializes the button component.
|
|
405
|
+
#
|
|
406
|
+
# @param [Hash] **props The properties to customize the button.
|
|
407
|
+
# @option props [String] :href The URL the button links to.
|
|
408
|
+
# @option props [Boolean] :active (false) Whether the button is currently active.
|
|
409
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
410
|
+
#
|
|
411
|
+
# @return [Button]
|
|
412
|
+
def initialize(**props, &block)
|
|
413
|
+
super(**props, &block)
|
|
414
|
+
@props = props
|
|
415
|
+
@parent_config = @props.delete(:parent_config)
|
|
416
|
+
|
|
417
|
+
@href = @props.delete(:href) || "#"
|
|
418
|
+
@active = options(@props.delete(:active), default: false)
|
|
419
|
+
|
|
420
|
+
add(class: styles[:button_group][:button], to: @props, first_element: true)
|
|
421
|
+
add(class: styles[:button_group][:button_active], to: @props) if @active
|
|
422
|
+
add(class: styles[:button_group][:button_inactive], to: @props) unless @active
|
|
423
|
+
|
|
424
|
+
@props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def call
|
|
428
|
+
@props[:data] ||= {}
|
|
429
|
+
@props[:data][:href] = @href
|
|
430
|
+
|
|
431
|
+
tag.button(type: "button", **@props) do
|
|
432
|
+
content
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::BreadcrumbComponent` renders a breadcrumb navigation following Flowbite styles.
|
|
4
|
+
# It uses slots for items, allowing each breadcrumb item to have an optional icon and a current_page flag.
|
|
5
|
+
class Fluxbit::BreadcrumbComponent < Fluxbit::Component
|
|
6
|
+
# Include configuration (default classes, etc.) for Breadcrumb, if defined
|
|
7
|
+
include Fluxbit::Config::BreadcrumbComponent
|
|
8
|
+
|
|
9
|
+
renders_many :items, lambda { |**attrs, &block|
|
|
10
|
+
item = Item.new(**attrs)
|
|
11
|
+
item.with_content(block.call) if block_given?
|
|
12
|
+
item
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Initialize with any HTML attributes (e.g., custom class or aria-label for the <nav>)
|
|
16
|
+
def initialize(**props)
|
|
17
|
+
super
|
|
18
|
+
@props = props
|
|
19
|
+
@props["aria-label"] ||= "Breadcrumb"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
tag.nav(**@props) do
|
|
24
|
+
tag.ol(safe_join(items), class: styles[:root][:list])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Item < Fluxbit::Component
|
|
29
|
+
include Fluxbit::Config::BreadcrumbComponent
|
|
30
|
+
renders_one :dropdown, Fluxbit::DropdownComponent
|
|
31
|
+
|
|
32
|
+
def initialize(**props)
|
|
33
|
+
super
|
|
34
|
+
@props = props
|
|
35
|
+
@current_page = @props.delete(:current_page)
|
|
36
|
+
@href = @props.delete(:href)
|
|
37
|
+
@icon = @props.delete(:icon)
|
|
38
|
+
@remove_dropdown_arrow = options(@props.delete(:remove_dropdown_arrow), default: false)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def call
|
|
42
|
+
item_content = content || ""
|
|
43
|
+
add class: styles[:item][:href][(@current_page || @href.blank?) ? :off : :on], to: @props
|
|
44
|
+
if dropdown? && !@remove_dropdown_arrow
|
|
45
|
+
item_content += chevron_down(class: "ms-3")
|
|
46
|
+
add class: styles[:item][:click_cursor], to: @props
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@props["data-dropdown-toggle"] = dropdown.get_item if dropdown?
|
|
50
|
+
|
|
51
|
+
tag.li(class: styles[:item][:base]) do
|
|
52
|
+
concat chevron_right(class: styles[:item][:chevron], stroke_width: 1)
|
|
53
|
+
if @current_page || @href.blank?
|
|
54
|
+
concat anyicon(@icon, class: styles[:item][:icon]) if @icon
|
|
55
|
+
concat tag.span(item_content, **@props)
|
|
56
|
+
else
|
|
57
|
+
concat(tag.a(href: @href, **@props) do
|
|
58
|
+
concat(anyicon(@icon, class: styles[:item][:icon])) if @icon
|
|
59
|
+
concat item_content
|
|
60
|
+
end)
|
|
61
|
+
end
|
|
62
|
+
concat(dropdown&.to_s || "")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -14,6 +14,7 @@ class Fluxbit::ButtonComponent < Fluxbit::Component
|
|
|
14
14
|
@tooltip_props = props
|
|
15
15
|
@tooltip_text = block.call
|
|
16
16
|
}
|
|
17
|
+
renders_one :dropdown, Fluxbit::DropdownComponent
|
|
17
18
|
|
|
18
19
|
##
|
|
19
20
|
# Initializes the button component with the given properties.
|
|
@@ -25,6 +26,7 @@ class Fluxbit::ButtonComponent < Fluxbit::Component
|
|
|
25
26
|
# @option props [Symbol, String] :color The color style of the button.
|
|
26
27
|
# @option props [Symbol, String] :size The size of the button (e.g., `0` to `4`).
|
|
27
28
|
# @option props [Boolean] :disabled (false) Sets the button to a disabled state.
|
|
29
|
+
# @option props [Boolean] :selected (false) Makes the button appear darker to indicate selected state.
|
|
28
30
|
# @option props [String] :remove_class ('') Classes to remove from the default class list.
|
|
29
31
|
# @option props [String] :popover_text (nil) Popover text (from Fluxbit::Component).
|
|
30
32
|
# @option props [Symbol] :popover_placement (:right) Popover placement (e.g., `:right`, `:left`, `:top`, `:bottom`) (from Fluxbit::Component).
|
|
@@ -39,15 +41,20 @@ class Fluxbit::ButtonComponent < Fluxbit::Component
|
|
|
39
41
|
super
|
|
40
42
|
@props = props
|
|
41
43
|
@form = @props.delete(:form)
|
|
42
|
-
@
|
|
43
|
-
@
|
|
44
|
-
@
|
|
45
|
-
@
|
|
46
|
-
@
|
|
47
|
-
@
|
|
48
|
-
@
|
|
44
|
+
@content = @props.delete(:content)
|
|
45
|
+
@as = options @props.delete(:as), default: @@as
|
|
46
|
+
@pill = options @props.delete(:pill), default: @@pill
|
|
47
|
+
@color = options (@props.delete(:color) || "").to_sym, collection: styles[:colors].keys, default: @@color
|
|
48
|
+
@selected = options @props.delete(:selected), default: @@selected
|
|
49
|
+
@grouped = options @props.delete(:grouped), default: false
|
|
50
|
+
@first_button = options @props.delete(:first_button), default: false
|
|
51
|
+
@last_button = options @props.delete(:last_button), default: false
|
|
52
|
+
@outline = @color.to_s.end_with?("_outline")
|
|
53
|
+
@full_sized = options(@props.delete(:full_sized), default: true)
|
|
54
|
+
@remove_dropdown_arrow = options(@props.delete(:remove_dropdown_arrow), default: false)
|
|
49
55
|
declare_size(@props.delete(:size) || @@size)
|
|
50
56
|
declare_disabled
|
|
57
|
+
declare_selected
|
|
51
58
|
declare_classes
|
|
52
59
|
@props[:class] = remove_class(@props.delete(:remove_class) || "", @props[:class])
|
|
53
60
|
end
|
|
@@ -72,6 +79,7 @@ class Fluxbit::ButtonComponent < Fluxbit::Component
|
|
|
72
79
|
to: @props,
|
|
73
80
|
first_element: true
|
|
74
81
|
)
|
|
82
|
+
add(class: styles[:full_sized], to: @props) if @full_sized
|
|
75
83
|
add(class: styles[:inner][:base], to: @props) if @grouped
|
|
76
84
|
add(class: styles[:inner][:position][:start], to: @props) if @grouped && @first_button
|
|
77
85
|
add(class: styles[:inner][:position][:end], to: @props) if @grouped && @last_button
|
|
@@ -84,14 +92,34 @@ class Fluxbit::ButtonComponent < Fluxbit::Component
|
|
|
84
92
|
add(class: styles[:disabled], to: @props, first_element: true)
|
|
85
93
|
end
|
|
86
94
|
|
|
95
|
+
def declare_selected
|
|
96
|
+
return unless @selected.present? && @selected == true
|
|
97
|
+
|
|
98
|
+
add(class: styles[:selected], to: @props, first_element: true)
|
|
99
|
+
end
|
|
100
|
+
|
|
87
101
|
def before_render
|
|
88
102
|
add_popover_or_tooltip
|
|
89
103
|
end
|
|
90
104
|
|
|
91
105
|
def call
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
106
|
+
@props["data-dropdown-toggle"] = dropdown.get_item if dropdown?
|
|
107
|
+
|
|
108
|
+
concat(render_button)
|
|
109
|
+
concat(render_popover_or_tooltip.to_s)
|
|
110
|
+
concat(dropdown&.to_s || "")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def render_button
|
|
116
|
+
button_content = @content || content || ""
|
|
117
|
+
|
|
118
|
+
if @form.nil?
|
|
119
|
+
button_content += dropdown? && !@remove_dropdown_arrow ? chevron_down(class: "ms-3") : ""
|
|
120
|
+
content_tag(@as, button_content, @props)
|
|
121
|
+
else
|
|
122
|
+
@form.submit(button_content, **@props)
|
|
123
|
+
end
|
|
96
124
|
end
|
|
97
125
|
end
|
|
@@ -27,7 +27,7 @@ class Fluxbit::ButtonGroupComponent < Fluxbit::Component
|
|
|
27
27
|
|
|
28
28
|
def call
|
|
29
29
|
buttons
|
|
30
|
-
|
|
30
|
+
tag.div(**@props) do
|
|
31
31
|
@buttons_group.each_with_index do |button, index|
|
|
32
32
|
button_props = button.props || {}
|
|
33
33
|
button_content = button.content
|