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,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The `Fluxbit::StepperComponent` is a customizable stepper component that extends `Fluxbit::Component`.
|
|
5
|
+
# It provides a visual representation of a multi-step process, supporting both horizontal and vertical orientations
|
|
6
|
+
# with various styling options including different colors and states.
|
|
7
|
+
#
|
|
8
|
+
# @example Basic usage
|
|
9
|
+
# = fx_stepper do |stepper|
|
|
10
|
+
# = stepper.with_step(title: "Step 1", state: :completed)
|
|
11
|
+
# = stepper.with_step(title: "Step 2", state: :active)
|
|
12
|
+
# = stepper.with_step(title: "Step 3")
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @see docs/02_Components/Stepper.md For detailed documentation.
|
|
16
|
+
class Fluxbit::StepperComponent < Fluxbit::Component
|
|
17
|
+
include Fluxbit::Config::StepperComponent
|
|
18
|
+
|
|
19
|
+
renders_many :steps, lambda { |**attrs, &block|
|
|
20
|
+
step = Step.new(**attrs)
|
|
21
|
+
step.with_content(block.call) if block_given?
|
|
22
|
+
step
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Initializes the stepper component with the given properties.
|
|
27
|
+
#
|
|
28
|
+
# @param [Hash] **props The properties to customize the stepper.
|
|
29
|
+
# @option props [Symbol] :orientation (:horizontal) The orientation of the stepper (:horizontal, :vertical).
|
|
30
|
+
# @option props [Symbol] :variant (:default) The variant of the stepper (:default, :progress, :detailed).
|
|
31
|
+
# @option props [Symbol] :color (:blue) The color theme of the active step (:blue, :green, :red, :yellow, :indigo, :purple).
|
|
32
|
+
# @option props [String] :remove_class ('') CSS classes to remove from the default class list.
|
|
33
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
34
|
+
#
|
|
35
|
+
# @return [Fluxbit::StepperComponent]
|
|
36
|
+
def initialize(**props)
|
|
37
|
+
super
|
|
38
|
+
@props = props
|
|
39
|
+
|
|
40
|
+
@orientation = options @props.delete(:orientation), collection: styles[:base].keys, default: @@orientation
|
|
41
|
+
@variant = options @props.delete(:variant), collection: styles[:list].keys, default: @@variant
|
|
42
|
+
@color = options @props.delete(:color), collection: styles[:step][:default][:active].keys, default: @@color
|
|
43
|
+
|
|
44
|
+
add class: styles[:base][@orientation], to: @props, first_element: true
|
|
45
|
+
remove_class_from_props(@props)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def call
|
|
49
|
+
tag.div(**@props) do
|
|
50
|
+
tag.ol(class: styles[:list][@variant][@orientation]) do
|
|
51
|
+
safe_join(steps.map.with_index { |step, index| render_step(step, index) })
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def render_step(step, index)
|
|
59
|
+
last_step = index == steps.count - 1
|
|
60
|
+
|
|
61
|
+
content_tag(:li, class: step_item_classes) do
|
|
62
|
+
if @orientation == :vertical && @variant != :detailed
|
|
63
|
+
# Vertical layout: wrapper div for proper positioning
|
|
64
|
+
concat(
|
|
65
|
+
tag.div(class: styles[:layout][:wrapper]) do
|
|
66
|
+
content = []
|
|
67
|
+
content << tag.div(class: styles[:layout][:flex_center]) do
|
|
68
|
+
concat(render_step_indicator(step))
|
|
69
|
+
concat(render_step_content(step)) if step.title.present? || step.description.present? || step.content.present?
|
|
70
|
+
end
|
|
71
|
+
content << render_vertical_connector(step) unless last_step
|
|
72
|
+
safe_join(content)
|
|
73
|
+
end
|
|
74
|
+
)
|
|
75
|
+
elsif @orientation == :vertical && @variant == :detailed
|
|
76
|
+
# Detailed vertical: no connectors, just cards
|
|
77
|
+
concat(render_step_indicator(step))
|
|
78
|
+
concat(render_step_content(step)) if step.title.present? || step.description.present? || step.content.present?
|
|
79
|
+
else
|
|
80
|
+
# Horizontal layout: standard flow with connectors
|
|
81
|
+
concat(render_step_indicator(step))
|
|
82
|
+
concat(render_step_content(step)) if step.title.present? || step.description.present? || step.content.present?
|
|
83
|
+
concat(render_connector(step)) unless last_step
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def step_item_classes
|
|
89
|
+
styles[:item][@variant][@orientation]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def render_step_indicator(step)
|
|
93
|
+
tag.div(class: step_circle_classes(step)) do
|
|
94
|
+
if step.icon.present?
|
|
95
|
+
anyicon(step.icon, class: styles[:step_icon][@variant][:completed])
|
|
96
|
+
elsif step.state == :completed && @variant == :progress
|
|
97
|
+
content_tag(:span, "✓", class: step_number_classes(step))
|
|
98
|
+
else
|
|
99
|
+
content_tag(:span, step.number || "", class: step_number_classes(step))
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def render_step_content(step)
|
|
105
|
+
return "" unless step.title.present? || step.description.present? || step.content.present?
|
|
106
|
+
|
|
107
|
+
tag.div(class: styles[:content][@variant][@orientation]) do
|
|
108
|
+
content = []
|
|
109
|
+
|
|
110
|
+
content << tag.h3(step.title, class: step_title_classes(step)) if step.title.present?
|
|
111
|
+
content << tag.p(step.description, class: styles[:description]) if step.description.present?
|
|
112
|
+
content << step.content if step.content.present?
|
|
113
|
+
|
|
114
|
+
safe_join(content)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def render_connector(step)
|
|
119
|
+
return "" if @orientation == :horizontal && steps.count <= 1
|
|
120
|
+
|
|
121
|
+
connector_classes = if step.state == :completed
|
|
122
|
+
styles[:connector][:completed][@variant][@orientation]
|
|
123
|
+
elsif step.state == :active
|
|
124
|
+
styles[:connector][:active][@color][@variant][@orientation]
|
|
125
|
+
else
|
|
126
|
+
styles[:connector][@variant][@orientation]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
tag.div(class: connector_classes)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def render_vertical_connector(step)
|
|
133
|
+
# Position based on variant (different circle sizes)
|
|
134
|
+
left_position = styles[:vertical_connector][:positions][@variant] || styles[:vertical_connector][:positions][:default]
|
|
135
|
+
|
|
136
|
+
connector_classes = [
|
|
137
|
+
styles[:vertical_connector][:base],
|
|
138
|
+
left_position,
|
|
139
|
+
vertical_connector_color(step)
|
|
140
|
+
].compact.join(" ")
|
|
141
|
+
|
|
142
|
+
tag.div(class: connector_classes)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def vertical_connector_color(step)
|
|
146
|
+
case step.state
|
|
147
|
+
when :completed
|
|
148
|
+
styles[:vertical_connector][:colors][:completed]
|
|
149
|
+
when :active
|
|
150
|
+
styles[:vertical_connector][:colors][@color] || styles[:vertical_connector][:colors][:blue]
|
|
151
|
+
else
|
|
152
|
+
styles[:vertical_connector][:colors][:default]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def step_circle_classes(step)
|
|
157
|
+
case step.state
|
|
158
|
+
when :completed
|
|
159
|
+
styles[:step][@variant][:completed]
|
|
160
|
+
when :active
|
|
161
|
+
styles[:step][@variant][:active][@color]
|
|
162
|
+
else
|
|
163
|
+
styles[:step][@variant][:base]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def step_number_classes(step)
|
|
168
|
+
case step.state
|
|
169
|
+
when :completed
|
|
170
|
+
styles[:step_number][@variant][:completed]
|
|
171
|
+
when :active
|
|
172
|
+
styles[:step_number][@variant][:active][@color]
|
|
173
|
+
else
|
|
174
|
+
styles[:step_number][@variant][:base]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def step_title_classes(step)
|
|
179
|
+
case step.state
|
|
180
|
+
when :completed
|
|
181
|
+
styles[:title][:completed]
|
|
182
|
+
when :active
|
|
183
|
+
styles[:title][:active][@color]
|
|
184
|
+
else
|
|
185
|
+
styles[:title][:base]
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Nested step component
|
|
190
|
+
class Step < Fluxbit::Component
|
|
191
|
+
include Fluxbit::Config::StepperComponent
|
|
192
|
+
|
|
193
|
+
attr_reader :title, :description, :state, :number, :icon
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# Initializes a step item for the stepper.
|
|
197
|
+
#
|
|
198
|
+
# @param [Hash] **props The properties to customize the step.
|
|
199
|
+
# @option props [String] :title The title of the step.
|
|
200
|
+
# @option props [String] :description The description of the step.
|
|
201
|
+
# @option props [Symbol] :state (:pending) The state of the step (:pending, :active, :completed).
|
|
202
|
+
# @option props [String, Integer] :number The step number or custom text.
|
|
203
|
+
# @option props [String] :icon The icon name to display for completed state.
|
|
204
|
+
#
|
|
205
|
+
# @return [Fluxbit::StepperComponent::Step]
|
|
206
|
+
def initialize(**props)
|
|
207
|
+
super
|
|
208
|
+
@props = props
|
|
209
|
+
|
|
210
|
+
@title = @props.delete(:title)
|
|
211
|
+
@description = @props.delete(:description)
|
|
212
|
+
@state = options @props.delete(:state), collection: [ :pending, :active, :completed ], default: :pending
|
|
213
|
+
@number = @props.delete(:number)
|
|
214
|
+
@icon = @props.delete(:icon)
|
|
215
|
+
|
|
216
|
+
remove_class_from_props(@props)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def call
|
|
220
|
+
content
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -13,13 +13,24 @@ class Fluxbit::TabComponent < Fluxbit::Component
|
|
|
13
13
|
end
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
# Initializes the Tab component with the given properties.
|
|
17
|
+
#
|
|
18
|
+
# @param [Hash] props The properties to customize the Tab component.
|
|
19
|
+
# @option props [Symbol] :variant (:default) The variant style (:default, :underline, :pills, :full_width).
|
|
20
|
+
# @option props [Symbol] :color (:blue) The color theme for active tabs.
|
|
21
|
+
# @option props [Boolean] :vertical (false) Whether tabs should be displayed vertically.
|
|
22
|
+
# @option props [Symbol] :tab_panel (:default) The style for tab panels (:default, :none).
|
|
23
|
+
# @option props [Symbol] :align (:left) Horizontal alignment of tabs (:left, :center, :right). Only applies to horizontal tabs.
|
|
24
|
+
# @option props [Hash] :ul_html ({}) HTML attributes to apply to the ul element.
|
|
25
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes, applied to the tab container.
|
|
16
26
|
def initialize(**props)
|
|
17
27
|
@variant = (props.delete(:variant) || @@variant).to_sym
|
|
18
28
|
@color = props.delete(:color) || @@color
|
|
19
29
|
@vertical = props.delete(:vertical) || @@vertical
|
|
20
30
|
@tab_panel = (props.delete(:tab_panel) || @@tab_panel).to_sym
|
|
31
|
+
@align = (props.delete(:align) || @@align).to_sym
|
|
21
32
|
@tabs_group = []
|
|
22
|
-
@
|
|
33
|
+
@ul_html = props.delete(:ul_html) || {}
|
|
23
34
|
@props = props
|
|
24
35
|
@vertical = false if @variant == :full_width
|
|
25
36
|
super
|
|
@@ -31,7 +42,7 @@ class Fluxbit::TabComponent < Fluxbit::Component
|
|
|
31
42
|
add class: styles[:div][@vertical ? :vertical : :horizontal], to: @props, first_element: true
|
|
32
43
|
|
|
33
44
|
if @has_panels
|
|
34
|
-
|
|
45
|
+
tag.div(**@props) do
|
|
35
46
|
concat(render_tab_list)
|
|
36
47
|
concat(render_tab_panels)
|
|
37
48
|
end
|
|
@@ -43,21 +54,22 @@ class Fluxbit::TabComponent < Fluxbit::Component
|
|
|
43
54
|
private
|
|
44
55
|
|
|
45
56
|
def render_tab_list
|
|
46
|
-
add class: styles[:tab_list][:ul][@vertical ? :vertical : :horizontal], to: @
|
|
47
|
-
add class: styles[:tab_list][:variant][variant], to: @
|
|
48
|
-
|
|
57
|
+
add class: styles[:tab_list][:ul][@vertical ? :vertical : :horizontal], to: @ul_html, first_element: true
|
|
58
|
+
add class: styles[:tab_list][:variant][variant], to: @ul_html
|
|
59
|
+
add class: styles[:tab_list][:align][@align], to: @ul_html unless @vertical
|
|
60
|
+
@ul_html[:role] = "tablist"
|
|
49
61
|
|
|
50
62
|
if @has_panels
|
|
51
|
-
@
|
|
63
|
+
@ul_html[:data] = {
|
|
52
64
|
"tabs-toggle": "##{fx_id}-content",
|
|
53
65
|
"tabs-active-classes": styles[:tab_list][:tab_item][:variant][variant][:active][@color],
|
|
54
66
|
"tabs-inactive-classes": styles[:tab_list][:tab_item][:variant][variant][:inactive]
|
|
55
67
|
}
|
|
56
68
|
end
|
|
57
69
|
|
|
58
|
-
@
|
|
70
|
+
@ul_html[:id] = fx_id
|
|
59
71
|
|
|
60
|
-
|
|
72
|
+
tag.ul(**@ul_html) do
|
|
61
73
|
safe_join(@tabs_group.map.with_index { |tab, index| render_tab(tab, index) })
|
|
62
74
|
end
|
|
63
75
|
end
|
|
@@ -102,41 +114,48 @@ class Fluxbit::TabComponent < Fluxbit::Component
|
|
|
102
114
|
tab.props.delete :"aria-controls"
|
|
103
115
|
end
|
|
104
116
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
add class: styles[:tab_list][:li], to:
|
|
117
|
+
li_html = tab.props.delete(:li_html) || {}
|
|
118
|
+
li_html[:role] = "presentation"
|
|
119
|
+
li_html[:id] = "#{fx_id}-#{index}-li"
|
|
120
|
+
add class: styles[:tab_list][:li], to: li_html, first_element: true
|
|
109
121
|
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
tag.li(**li_html) do
|
|
123
|
+
tag.a(**tab.props) do
|
|
112
124
|
concat(render_icon(tab_icon)) if tab_icon
|
|
113
|
-
concat(
|
|
125
|
+
concat(tag.span(tab_title))
|
|
114
126
|
end
|
|
115
127
|
end
|
|
116
128
|
end
|
|
117
129
|
|
|
118
130
|
def render_icon(icon)
|
|
119
|
-
if icon.
|
|
120
|
-
|
|
131
|
+
return "" if icon.blank?
|
|
132
|
+
|
|
133
|
+
modified = if icon.include?('class="')
|
|
134
|
+
icon.gsub('class="', "class=\"#{styles[:tab_list][:tab_item][:icon]} ")
|
|
121
135
|
else
|
|
122
136
|
icon.gsub("<svg", "<svg class=\"#{styles[:tab_list][:tab_item][:icon]}\"")
|
|
123
|
-
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
allowed_tags = %w[svg path circle rect line polyline polygon g defs title use]
|
|
140
|
+
allowed_attrs = %w[class fill stroke stroke-width stroke-linecap stroke-linejoin width height viewBox xmlns x y d points cx cy r x1 y1 x2 y2 href aria-hidden]
|
|
141
|
+
|
|
142
|
+
view_context.sanitize(modified, tags: allowed_tags, attributes: allowed_attrs)
|
|
124
143
|
end
|
|
125
144
|
|
|
126
145
|
def render_tab_panels
|
|
127
|
-
|
|
146
|
+
tag.div(id: "##{fx_id}-content", class: styles[:tabpanel_container][@vertical ? :vertical : :horizontal]) do
|
|
128
147
|
safe_join(@tabs_group.map.with_index { |tab, index| render_tabpanel(tab, index) })
|
|
129
148
|
end
|
|
130
149
|
end
|
|
131
150
|
|
|
132
151
|
def render_tabpanel(tab, index)
|
|
133
|
-
|
|
134
|
-
add class: styles[:tabpanel][@vertical ? :vertical : :horizontal][@tab_panel][tab.props[:active] ? :active : :inactive], to:
|
|
152
|
+
content_html = tab.props[:content_html] || {}
|
|
153
|
+
add class: styles[:tabpanel][@vertical ? :vertical : :horizontal][@tab_panel][tab.props[:active] ? :active : :inactive], to: content_html, first_element: true
|
|
135
154
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
155
|
+
content_html[:id] = "#{fx_id}-tabpanel-#{index}"
|
|
156
|
+
content_html[:role] = "tabpanel"
|
|
157
|
+
content_html[:"aria-labelledby"] = "#{fx_id}-#{index}"
|
|
139
158
|
|
|
140
|
-
|
|
159
|
+
tag.div(tab.content, **content_html)
|
|
141
160
|
end
|
|
142
161
|
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The `Fluxbit::TableComponent` 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::TableComponent.new(
|
|
10
|
+
# striped: true,
|
|
11
|
+
# bordered: true,
|
|
12
|
+
# hover: true,
|
|
13
|
+
# shadow: true,
|
|
14
|
+
# wrapper_html: { class: "my-custom-wrapper" },
|
|
15
|
+
# thead_html: { class: "my-custom-thead" },
|
|
16
|
+
# tbody_html: { class: "my-custom-tbody" },
|
|
17
|
+
# tr_html: { class: "my-custom-tr" }
|
|
18
|
+
# ) do |table|
|
|
19
|
+
# table.with_header do |header|
|
|
20
|
+
# header.with_column "Column 1"
|
|
21
|
+
# header.with_column "Column 2"
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# table.with_row do |row|
|
|
25
|
+
# row.with_cell "Data 1"
|
|
26
|
+
# row.with_cell "Data 2"
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
class Fluxbit::TableComponent < Fluxbit::Component
|
|
31
|
+
include Fluxbit::Config::TableComponent
|
|
32
|
+
|
|
33
|
+
renders_one :footer, lambda { |*args, **props, &block|
|
|
34
|
+
add class: styles[:footer][:base], to: props
|
|
35
|
+
props[:as] ||= :tr
|
|
36
|
+
remove_class_from_props props
|
|
37
|
+
|
|
38
|
+
props[:cells_html] ||= {}
|
|
39
|
+
props[:cells_html][:as] ||= :td
|
|
40
|
+
props[:cells_html][:scope] ||= :row
|
|
41
|
+
add(class: styles[:footer][:cell], to: props[:cells_html])
|
|
42
|
+
remove_class_from_props props[:cells_html]
|
|
43
|
+
|
|
44
|
+
Fluxbit::TableGroupComponent.new(*args, **props, &block)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
renders_many :headers, lambda { |*args, **props, &block|
|
|
48
|
+
add class: styles[:head][:base], to: props
|
|
49
|
+
props[:as] ||= :tr
|
|
50
|
+
remove_class_from_props props
|
|
51
|
+
|
|
52
|
+
props[:cells_html] ||= {}
|
|
53
|
+
props[:cells_html][:as] ||= :th
|
|
54
|
+
props[:cells_html][:scope] ||= :col
|
|
55
|
+
add(class: styles[:head][:cell], to: props[:cells_html])
|
|
56
|
+
remove_class_from_props props[:cells_html]
|
|
57
|
+
|
|
58
|
+
Fluxbit::TableGroupComponent.new(*args, **props, &block)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
renders_many :rows, lambda { |*args, **props, &block|
|
|
62
|
+
color = props.delete(:color) || :default
|
|
63
|
+
add(class: styles[:row][:base], to: props)
|
|
64
|
+
add(class: styles[:row][:striped][color], to: props) if @striped
|
|
65
|
+
add(class: styles[:row][:hovered][color], to: props) if @hover
|
|
66
|
+
add(class: styles[:row][:bordered], to: props) if @bordered
|
|
67
|
+
add(class: styles[:row][:colors][color], to: props, first_element: true) if styles[:row][:colors].key?(color) && !@striped
|
|
68
|
+
props[:as] ||= :tr
|
|
69
|
+
remove_class_from_props props
|
|
70
|
+
|
|
71
|
+
props[:cells_html] ||= {}
|
|
72
|
+
props[:cells_html][:as] ||= :td
|
|
73
|
+
props[:cells_html][:scope] ||= :row
|
|
74
|
+
add(class: styles[:row][:cell][:base], to: props[:cells_html])
|
|
75
|
+
remove_class_from_props props[:cells_html]
|
|
76
|
+
|
|
77
|
+
Fluxbit::TableGroupComponent.new(*args, **props, &block)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Initializes the table component with the given properties.
|
|
82
|
+
#
|
|
83
|
+
# @param [Hash] props The properties to customize the table.
|
|
84
|
+
# @option props [Boolean] :striped (false) Determines if the table rows should be striped.
|
|
85
|
+
# @option props [Boolean] :bordered (false) Determines if the table should have borders.
|
|
86
|
+
# @option props [Boolean] :hover (false) Determines if the table rows should highlight on hover.
|
|
87
|
+
# @option props [Boolean] :shadow (false) Determines if the table should have a shadow effect.
|
|
88
|
+
# @option props [Hash] :thead_html Additional HTML attributes for the table header.
|
|
89
|
+
# @option props [Hash] :tbody_html Additional HTML attributes for the table body.
|
|
90
|
+
# @option props [Hash] :tr_html Additional HTML attributes for the table rows.
|
|
91
|
+
# @option props [Hash] :cells_html Additional HTML attributes for the table cells.
|
|
92
|
+
#
|
|
93
|
+
# @example
|
|
94
|
+
# = render Fluxbit::TableComponent.new(
|
|
95
|
+
# striped: true,
|
|
96
|
+
# bordered: true,
|
|
97
|
+
# hover: true,
|
|
98
|
+
# shadow: true,
|
|
99
|
+
# thead_html: { class: "my-custom-thead" },
|
|
100
|
+
# tbody_html: { class: "my-custom-tbody" },
|
|
101
|
+
# tr_html: { class: "my-custom-tr" }
|
|
102
|
+
# ) do |table|
|
|
103
|
+
# table.with_header do |header|
|
|
104
|
+
# header.with_column "Column 1"
|
|
105
|
+
# header.with_column "Column 2"
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# table.with_row do |row|
|
|
109
|
+
# row.with_cell "Data 1"
|
|
110
|
+
# row.with_cell "Data 2"
|
|
111
|
+
# end
|
|
112
|
+
# end
|
|
113
|
+
#
|
|
114
|
+
# @return [Fluxbit::TableComponent]
|
|
115
|
+
#
|
|
116
|
+
def initialize(**props)
|
|
117
|
+
super
|
|
118
|
+
@props = props
|
|
119
|
+
@striped = options @props.delete(:striped), default: @@striped
|
|
120
|
+
@bordered = options @props.delete(:bordered), default: @@bordered
|
|
121
|
+
@hover = options @props.delete(:hover), default: @@hover
|
|
122
|
+
@shadow = options @props.delete(:shadow), default: @@shadow
|
|
123
|
+
@only_rows = options @props.delete(:only_rows), default: false
|
|
124
|
+
|
|
125
|
+
# Wrapper HTML
|
|
126
|
+
@wrapper_html = @props.delete(:wrapper_html) || {}
|
|
127
|
+
add(class: styles[:wrapper][:base], to: @wrapper_html)
|
|
128
|
+
add(class: styles[:wrapper][:shadow], to: @wrapper_html) if @shadow
|
|
129
|
+
|
|
130
|
+
# Head HTML
|
|
131
|
+
@thead_html = @props.delete(:thead_html) || {}
|
|
132
|
+
# add(class: styles[:head][:base], to: @thead_html)
|
|
133
|
+
|
|
134
|
+
# Body HTML
|
|
135
|
+
@tbody_html = @props.delete(:tbody_html) || {}
|
|
136
|
+
add(class: styles[:body][:base], to: @tbody_html)
|
|
137
|
+
|
|
138
|
+
# Row HTML
|
|
139
|
+
@tr_html = @props.delete(:tr_html) || {}
|
|
140
|
+
add(class: styles[:body][:base], to: @tbody_html)
|
|
141
|
+
|
|
142
|
+
# Footer HTML
|
|
143
|
+
@tfoot_html = @props.delete(:tfoot_html) || {}
|
|
144
|
+
add(class: styles[:footer][:base], to: @tfoot_html)
|
|
145
|
+
|
|
146
|
+
# Table HTML
|
|
147
|
+
add(class: styles[:root][:base], to: @props)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def call
|
|
151
|
+
return safe_join(rows) if @only_rows && rows?
|
|
152
|
+
|
|
153
|
+
# Wrapper
|
|
154
|
+
capture do
|
|
155
|
+
# Table
|
|
156
|
+
concat(tag.div(**@wrapper_html) do
|
|
157
|
+
concat(tag.table(**@props) do
|
|
158
|
+
# header
|
|
159
|
+
concat(
|
|
160
|
+
tag.thead(**@thead_html) do
|
|
161
|
+
concat(safe_join headers)
|
|
162
|
+
end
|
|
163
|
+
) if headers?
|
|
164
|
+
|
|
165
|
+
# body
|
|
166
|
+
concat(
|
|
167
|
+
tag.tbody(**@tbody_html) do
|
|
168
|
+
if content.present?
|
|
169
|
+
content
|
|
170
|
+
else
|
|
171
|
+
concat(safe_join(rows)) if rows?
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Footer
|
|
177
|
+
concat(
|
|
178
|
+
tag.tfoot(**@tfoot_html) do
|
|
179
|
+
concat(footer)
|
|
180
|
+
end
|
|
181
|
+
) if footer?
|
|
182
|
+
end)
|
|
183
|
+
end)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Fluxbit::TableGroupComponent < Fluxbit::Component
|
|
4
|
+
include Fluxbit::Config::TableComponent
|
|
5
|
+
|
|
6
|
+
renders_many :cells, lambda { |*args, **props, &block|
|
|
7
|
+
cells_html_now = @cells_html.dup
|
|
8
|
+
add(class: props[:class], to: cells_html_now)
|
|
9
|
+
cells_html_now = props.merge cells_html_now
|
|
10
|
+
add(class: styles[:row][:cell][:selected], to: cells_html_now) if props.delete(:selected) || false
|
|
11
|
+
remove_class_from_props(cells_html_now)
|
|
12
|
+
|
|
13
|
+
content_tag(@as_cells, block.call, **cells_html_now)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
def initialize(**props)
|
|
17
|
+
super
|
|
18
|
+
@props = props
|
|
19
|
+
@as = @props.delete(:as) || :tr
|
|
20
|
+
@cells_html = @props.delete(:cells_html) || { as: :td }
|
|
21
|
+
|
|
22
|
+
@as_cells = @cells_html.delete(:as) || :td
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
tag.tr(safe_join(cells), **@props)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The `Fluxbit::ThemeButtonComponent` is a round button component that toggles between dark, light, and system themes.
|
|
5
|
+
# It extends `Fluxbit::ButtonComponent` and automatically updates the theme when clicked.
|
|
6
|
+
class Fluxbit::ThemeButtonComponent < Fluxbit::ButtonComponent
|
|
7
|
+
include Fluxbit::Config::ThemeButtonComponent
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Initializes the theme button component with the given properties.
|
|
11
|
+
#
|
|
12
|
+
# @param [Hash] props The properties to customize the theme button.
|
|
13
|
+
# @option props [Symbol, String] :color The color style of the button (default: :transparent)
|
|
14
|
+
# @option props [Symbol, String] :size The size of the button (default: 2)
|
|
15
|
+
# @option props [Boolean] :pill (true) Makes the button round
|
|
16
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
17
|
+
#
|
|
18
|
+
# @return [Fluxbit::ThemeButtonComponent]
|
|
19
|
+
def initialize(**props)
|
|
20
|
+
# Set default values specific to theme button
|
|
21
|
+
props[:pill] = true unless props.key?(:pill)
|
|
22
|
+
props[:color] ||= :transparent
|
|
23
|
+
props[:size] ||= 2
|
|
24
|
+
props[:remove_dropdown_arrow] = true
|
|
25
|
+
|
|
26
|
+
# Add Stimulus controller
|
|
27
|
+
props["data-controller"] = [props["data-controller"], "fx-theme-button"].compact.join(" ")
|
|
28
|
+
props["data-action"] = [props["data-action"], "click->fx-theme-button#toggle"].compact.join(" ")
|
|
29
|
+
|
|
30
|
+
super(**props)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call
|
|
34
|
+
concat(render_theme_button)
|
|
35
|
+
concat(render_popover_or_tooltip.to_s)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def render_theme_button
|
|
41
|
+
button_content = safe_join([
|
|
42
|
+
content_tag(:span, light_icon, class: "fx-theme-icon fx-theme-light hidden", "data-fx-theme-button-target": "lightIcon"),
|
|
43
|
+
content_tag(:span, dark_icon, class: "fx-theme-icon fx-theme-dark hidden", "data-fx-theme-button-target": "darkIcon"),
|
|
44
|
+
content_tag(:span, system_icon, class: "fx-theme-icon fx-theme-system hidden", "data-fx-theme-button-target": "systemIcon")
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
content_tag(@as, button_content, @props)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def light_icon
|
|
51
|
+
# Sun icon for light mode
|
|
52
|
+
anyicon("heroicons_outline:sun", class: "size-5")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def dark_icon
|
|
56
|
+
# Moon icon for dark mode
|
|
57
|
+
anyicon("heroicons_outline:moon", class: "size-5")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def system_icon
|
|
61
|
+
# Computer/display icon for system mode
|
|
62
|
+
anyicon("heroicons_outline:computer-desktop", class: "size-5")
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The `Fluxbit::TimelineComponent` is a component for rendering customizable timelines.
|
|
4
|
+
class Fluxbit::TimelineComponent < Fluxbit::Component
|
|
5
|
+
include Fluxbit::Config::TimelineComponent
|
|
6
|
+
|
|
7
|
+
# Slot for timeline items
|
|
8
|
+
renders_many :items, lambda { |**props|
|
|
9
|
+
Fluxbit::TimelineItemComponent.new(
|
|
10
|
+
variant: @variant,
|
|
11
|
+
**props
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Initializes the Timeline component with various customization options.
|
|
16
|
+
#
|
|
17
|
+
# @param [Hash] **props The properties to customize the timeline.
|
|
18
|
+
# @option props [Symbol] :variant (:default) The timeline variant (:default, :vertical, :stepper, :activity).
|
|
19
|
+
# @option props [Symbol] :position (:left) The position of timeline indicators (:left, :center, :right).
|
|
20
|
+
# @option props [String] :remove_class ('') CSS classes to remove from the default class list.
|
|
21
|
+
# @option props [Hash] **props Remaining options declared as HTML attributes.
|
|
22
|
+
#
|
|
23
|
+
# @return [Fluxbit::TimelineComponent]
|
|
24
|
+
def initialize(**props)
|
|
25
|
+
super
|
|
26
|
+
@props = props
|
|
27
|
+
|
|
28
|
+
@variant = options @props.delete(:variant), collection: styles[:variants].keys, default: @@variant
|
|
29
|
+
@position = options @props.delete(:position), collection: styles[:positions].keys, default: @@position
|
|
30
|
+
|
|
31
|
+
add class: [
|
|
32
|
+
styles[:base],
|
|
33
|
+
styles[:variants][@variant],
|
|
34
|
+
styles[:positions][@position]
|
|
35
|
+
], to: @props
|
|
36
|
+
|
|
37
|
+
remove_class_from_props(@props)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def call
|
|
41
|
+
content_tag(@variant == :stepper ? "ol" : "ul", **@props) do
|
|
42
|
+
items_with_position.join.html_safe
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def items_with_position
|
|
49
|
+
items.map.with_index do |item, index|
|
|
50
|
+
item.instance_variable_set(:@is_last, index == items.length - 1)
|
|
51
|
+
item
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def timeline_classes
|
|
57
|
+
[
|
|
58
|
+
styles[:base],
|
|
59
|
+
styles[:variants][@variant],
|
|
60
|
+
styles[:positions][@position]
|
|
61
|
+
].compact.join(" ")
|
|
62
|
+
end
|
|
63
|
+
end
|