fluxbit_view_components 0.5.0 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f66b9f23ca4ef3e2fe559a212875eda72091ec0e44473efdeade01655b01ddf
4
- data.tar.gz: 5d17abad070b64489453dde8cc700f3c464d4b97d60f0cb8aab4aa0137013385
3
+ metadata.gz: bd7bed7240f33dbb0940a142d872dc031c7ad7bd2f065f1051ef2b2eb5dd4abe
4
+ data.tar.gz: dad11a6840d1835cbf7f7c2ff03d37bf70f1d1bc708c00785bf0b0e09f954796
5
5
  SHA512:
6
- metadata.gz: 0f893f775299b24f11ebc2c8120099c13999f525502d314d9ad87709a02fbbf6dd194bede20193e0cf5a5a7bd53b2b9e58d1298113df77f3ee7b958988c81bfd
7
- data.tar.gz: 802ffce4186aa7fe55a32c56b904e084b4c346b6e50a5891bab9683eaa636cde5654b97e0c9b4502e30ad20e4d21b4bdcb2e68260cd66fd9d1aad72a7fd1ea73
6
+ metadata.gz: 0c2278a2d6995c3247cadc40c50baa567f4dccade78c4e6aea44d0df001c206dd9cea61a2b94ef3644e9badb1f0b6c090843e6cf5c1be7b7b9cb675d3ebee3bb
7
+ data.tar.gz: eb413e15da3790ed9c5b39b2966153b23ec8d88ddb9fa8b0a452318cdf15304f60ac75066ef92f7a65ff7f3fb78cf4c28a671962a63f5487d7a3417fd28ae8ed
@@ -6,7 +6,10 @@ class Fluxbit::AccordionComponent < Fluxbit::Component
6
6
  include Fluxbit::Config::AccordionComponent
7
7
 
8
8
  renders_many :panels, lambda { |**attrs, &block|
9
+ @panel_index ||= 0
10
+ attrs[:index] = @panel_index unless attrs.key?(:index)
9
11
  panel = Panel.new(accordion_id: fx_id, flush: @flush, color: @color, **attrs)
12
+ @panel_index += 1
10
13
  block.call(panel) if block_given?
11
14
  panel
12
15
  }
@@ -58,7 +61,7 @@ class Fluxbit::AccordionComponent < Fluxbit::Component
58
61
  # @param [Boolean] flush (false) Whether the panel should use flush styling.
59
62
  # @param [Symbol, String] color (:default) The color theme for this panel.
60
63
  # @param [Boolean] open (false) Whether the panel should start in an expanded state.
61
- # @param [Integer] index (0) The panel's position index for proper styling (first, middle, last).
64
+ # @param [Integer] index The panel's position index for proper styling (first, middle, last). Automatically increments for each panel if not specified.
62
65
  # @param [Hash] **props Additional HTML attributes for the panel container.
63
66
  #
64
67
  # @return [Fluxbit::AccordionComponent::Panel]
@@ -99,7 +99,7 @@ class Fluxbit::Form::TextFieldComponent < Fluxbit::Form::FieldComponent
99
99
  first_element: true,
100
100
  class: [
101
101
  styles[:default],
102
- (@props.key?(:readonly) || @props.key?(:disabled) ? styles[:text][@color] : nil),
102
+ (@props.key?(:readonly) || @props.key?(:disabled) ? styles[:text][@color] : styles[:text][:default]),
103
103
  styles[:ring][@color],
104
104
  styles[:bg][@color],
105
105
  styles[:placeholder][@color],
@@ -1,10 +1,10 @@
1
1
  <%= content_tag :div, **@wrapper_html do %>
2
- <div id="<%= id %>" class="mt-6 grow lg:mt-0 lg:ml-6 lg:shrink-0 lg:grow-0">
2
+ <div id="<%= id %>" class="grow lg:mt-0 lg:shrink-0 lg:grow-0">
3
3
  <%= label %>
4
4
  <div class="mt-1 lg:hidden">
5
5
  <div class="flex items-center">
6
6
  <div class="inline-block h-12 w-12 shrink-0 overflow-hidden <%= container_rounded_class %> relative" aria-hidden="true">
7
- <%= image_element %>
7
+ <%= has_initials? ? initials_element : image_element %>
8
8
  </div>
9
9
  <div class="ml-5 rounded-md shadow-xs">
10
10
  <div class="group relative flex items-center justify-center rounded-md border border-slate-300 py-2 px-3 focus-within:ring-2 focus-within:ring-sky-500 focus-within:ring-offset-2 hover:bg-slate-50">
@@ -18,11 +18,11 @@
18
18
  </div>
19
19
  </div>
20
20
 
21
- <div class="relative hidden overflow-hidden <%= container_rounded_class %> lg:block w-40">
22
- <div class="inline-block h-40 w-40 shrink-0 overflow-hidden <%= container_rounded_class %> relative" aria-hidden="true">
23
- <%= image_element %>
21
+ <div class="relative hidden overflow-hidden <%= container_rounded_class %> lg:block w-40 h-40">
22
+ <div class="absolute inset-0 w-full h-full overflow-hidden <%= container_rounded_class %>" aria-hidden="true">
23
+ <%= has_initials? ? initials_element : image_element %>
24
24
  </div>
25
- <label for="desktop-<%= id %>" class="absolute inset-0 flex flex-col h-full w-full items-center justify-center bg-blue-800/75 text-sm font-medium text-white opacity-0 hover:opacity-100">
25
+ <label for="desktop-<%= id %>" class="absolute inset-0 flex flex-col items-center justify-center bg-blue-800/75 text-sm font-medium text-white opacity-0 hover:opacity-100 <%= container_rounded_class %>">
26
26
  <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-white mb-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
27
27
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
28
28
  </svg>
@@ -36,12 +36,25 @@
36
36
 
37
37
  <script>
38
38
  function loadFile(event, id) {
39
- const images = document.querySelectorAll('.img_photo_' + id);
39
+ const elements = document.querySelectorAll('.img_photo_' + id);
40
40
  if (event.target.files && event.target.files[0]) {
41
- images.forEach(function(img) {
42
- img.src = URL.createObjectURL(event.target.files[0]);
43
- img.onload = function() {
44
- URL.revokeObjectURL(img.src);
41
+ elements.forEach(function(element) {
42
+ if (element.hasAttribute('data-initials-placeholder')) {
43
+ const img = document.createElement('img');
44
+ img.className = element.className.replace('flex items-center justify-center', '');
45
+ img.alt = element.querySelector('span')?.textContent || 'Uploaded image';
46
+ img.classList.remove('bg-gradient-to-br', 'from-blue-500', 'to-purple-600', 'from-green-500', 'to-teal-600', 'from-pink-500', 'to-rose-600', 'from-orange-500', 'to-red-600', 'from-indigo-500', 'to-blue-600', 'from-purple-500', 'to-pink-600', 'from-cyan-500', 'to-blue-600', 'from-emerald-500', 'to-green-600', 'from-amber-500', 'to-orange-600', 'from-violet-500', 'to-purple-600');
47
+ img.classList.add('object-cover');
48
+ img.src = URL.createObjectURL(event.target.files[0]);
49
+ img.onload = function() {
50
+ URL.revokeObjectURL(img.src);
51
+ }
52
+ element.parentNode.replaceChild(img, element);
53
+ } else {
54
+ element.src = URL.createObjectURL(event.target.files[0]);
55
+ element.onload = function() {
56
+ URL.revokeObjectURL(element.src);
57
+ }
45
58
  }
46
59
  });
47
60
  }
@@ -21,6 +21,7 @@ class Fluxbit::Form::UploadImageComponent < Fluxbit::Form::FieldComponent
21
21
  # @param helper_popover_placement [String] Placement of the popover (default: "right")
22
22
  # @param image_path [String] Path to the image to be displayed (optional)
23
23
  # @param image_placeholder [String] Placeholder image path if no image is attached (optional)
24
+ # @param initials [String] Initials to display as placeholder (e.g., "JD" for John Doe) - overrides image_placeholder
24
25
  # @param title [Boolean, String] Whether to show a title (true for default, false to hide, or custom string)
25
26
  # @param rounded [Boolean] Whether to show image as circle (true, default) or square with rounded edges (false)
26
27
  # @param class [String] Additional CSS classes for the input element
@@ -30,6 +31,7 @@ class Fluxbit::Form::UploadImageComponent < Fluxbit::Form::FieldComponent
30
31
  @title = @props.delete(:title) || "Change"
31
32
  @rounded = @props.delete(:rounded)
32
33
  @rounded = true if @rounded.nil?
34
+ @initials = @props.delete(:initials)
33
35
  @image_path = @props.delete(:image_path) ||
34
36
  (if @object&.send(@attribute).respond_to?(:attached?) && @object&.send(@attribute)&.send("attached?")
35
37
  @object&.send(@attribute)&.variant(resize_to_fit: [ 160, 160 ])
@@ -58,4 +60,51 @@ class Fluxbit::Form::UploadImageComponent < Fluxbit::Form::FieldComponent
58
60
  def image_rounded_class
59
61
  @rounded ? "rounded-full" : "rounded-lg"
60
62
  end
63
+
64
+ def has_initials?
65
+ @initials.present?
66
+ end
67
+
68
+ def initials_element
69
+ return unless has_initials?
70
+
71
+ content_tag :div,
72
+ class: "img_photo_#{id} img_photo absolute inset-0 w-full h-full flex items-center justify-center #{image_rounded_class} #{initials_gradient_class}",
73
+ data: { initials_placeholder: true } do
74
+ content_tag :span, @initials.upcase, class: "text-white font-bold #{initials_text_size_class}"
75
+ end
76
+ end
77
+
78
+ def initials_gradient_class
79
+ # Generate a consistent gradient based on the initials hash
80
+ gradient_index = @initials.sum { |c| c.ord } % gradient_options.length
81
+ gradient_options[gradient_index]
82
+ end
83
+
84
+ def initials_text_size_class
85
+ # Smaller initials (1-2 chars) get larger text, longer ones get smaller text
86
+ case @initials.length
87
+ when 1..2
88
+ "text-4xl"
89
+ when 3
90
+ "text-3xl"
91
+ else
92
+ "text-2xl"
93
+ end
94
+ end
95
+
96
+ def gradient_options
97
+ [
98
+ "bg-gradient-to-br from-blue-500 to-purple-600",
99
+ "bg-gradient-to-br from-green-500 to-teal-600",
100
+ "bg-gradient-to-br from-pink-500 to-rose-600",
101
+ "bg-gradient-to-br from-orange-500 to-red-600",
102
+ "bg-gradient-to-br from-indigo-500 to-blue-600",
103
+ "bg-gradient-to-br from-purple-500 to-pink-600",
104
+ "bg-gradient-to-br from-cyan-500 to-blue-600",
105
+ "bg-gradient-to-br from-emerald-500 to-green-600",
106
+ "bg-gradient-to-br from-amber-500 to-orange-600",
107
+ "bg-gradient-to-br from-violet-500 to-purple-600"
108
+ ]
109
+ end
61
110
  end
@@ -19,6 +19,8 @@ class Fluxbit::GravatarComponent < Fluxbit::AvatarComponent
19
19
  #
20
20
  # @param [Hash] props The properties to customize the Gravatar.
21
21
  # @option props [String] :email The email address associated with the Gravatar.
22
+ # @option props [String] :name The display name for the Gravatar (used with :initials and :color defaults).
23
+ # @option props [String] :initials Custom initials to display (used with :initials default).
22
24
  # @option props [Symbol] :rating (:g) The rating of the Gravatar (:g, :pg, :r, :x).
23
25
  # @option props [Boolean] :secure (true) Whether to use HTTPS for the Gravatar URL.
24
26
  # @option props [Symbol] :filetype (:png) The filetype of the Gravatar (:png, :jpg, :gif).
@@ -34,7 +36,9 @@ class Fluxbit::GravatarComponent < Fluxbit::AvatarComponent
34
36
  secure: options(@props.delete(:secure), default: true),
35
37
  filetype: options((@props.delete(:filetype)|| "").to_sym, collection: gravatar_styles[:filetype], default: @@filetype),
36
38
  default: options((@props.delete(:default)|| "").to_sym, collection: gravatar_styles[:default], default: @@default),
37
- size: gravatar_styles[:size][options(@props[:size], collection: gravatar_styles[:size], default: @@size)]
39
+ size: gravatar_styles[:size][options(@props[:size], collection: gravatar_styles[:size], default: @@size)],
40
+ name: @props.delete(:name),
41
+ initials: @props.delete(:initials)
38
42
  }
39
43
  add class: gravatar_styles[:base], to: @props
40
44
  @email = @props.delete(:email)
@@ -88,6 +92,8 @@ class Fluxbit::GravatarComponent < Fluxbit::AvatarComponent
88
92
  case key
89
93
  when :forcedefault
90
94
  processed_options[key] = "y" if val
95
+ when :name, :initials
96
+ processed_options[key] = val if val.present?
91
97
  else
92
98
  processed_options[key] = val
93
99
  end
@@ -3,13 +3,13 @@
3
3
  module Fluxbit::Config::GravatarComponent
4
4
  mattr_accessor :rating, default: :pg
5
5
  mattr_accessor :filetype, default: :png
6
- mattr_accessor :default, default: :robohash # options: 404, mp (mystery person), identicon, monsterid, wavatar, retro, robohash, blank
6
+ mattr_accessor :default, default: :robohash # options: 404, mp (mystery person), identicon, monsterid, wavatar, retro, robohash, blank, initials, color
7
7
 
8
8
  # rubocop: disable Layout/LineLength, Metrics/BlockLength
9
9
  mattr_accessor :gravatar_styles do
10
10
  {
11
11
  base: "bg-gray-200 dark:bg-gray-600",
12
- default: %i[404 mp identicon monsterid wavatar retro robohash blank],
12
+ default: %i[404 mp identicon monsterid wavatar retro robohash blank initials color],
13
13
  size: { xs: 30, sm: 40, md: 50, lg: 100, xl: 200 },
14
14
  rating: %i[g pg r x],
15
15
  filetype: %i[jpg jpeg gif png heic]
@@ -1,5 +1,5 @@
1
1
  module Fluxbit
2
2
  module ViewComponents
3
- VERSION = "0.5.0"
3
+ VERSION = "0.5.2"
4
4
  end
5
5
  end
@@ -89,6 +89,8 @@ module Fluxbit
89
89
  # Generate i18n
90
90
  template "i18n.en.yml.tt", File.join("config", "locales", "#{file_name.pluralize}.en.yml")
91
91
  template "i18n.pt-BR.yml.tt", File.join("config", "locales", "#{file_name.pluralize}.pt-BR.yml")
92
+ template "i18n.general.en.yml.tt", File.join("config", "locales", "general.en.yml")
93
+ template "i18n.general.pt-BR.yml.tt", File.join("config", "locales", "general.pt-BR.yml")
92
94
 
93
95
  # Generate Policy
94
96
  template "policy.rb.tt", File.join("app/policies", "#{file_name.singularize}_policy.rb")
@@ -97,6 +99,7 @@ module Fluxbit
97
99
  template "_alert.html.erb.tt", File.join("app/views/shared", "_alert.html.erb")
98
100
  template "send_alert_via_drawer.html.erb.tt", File.join("app/views/shared", "send_alert_via_drawer_alert.html.erb")
99
101
  template "_flash.html.erb.tt", File.join("app/views/shared", "_flash.html.erb")
102
+ template "sortable.rb.tt", File.join("app/controllers/concerns", "sortable.rb")
100
103
  end
101
104
 
102
105
  private
@@ -6,10 +6,12 @@
6
6
  "warning" => :warning
7
7
  } %>
8
8
 
9
- <%% flash.each do |type, msg| %>
10
- <%% next if msg.blank? %>
11
- <%% color = mapping[type.to_s] || :info %>
12
- <%%= fx_alert(color: color) do %>
13
- <%%= safe_join(Array(msg).map { |m| sanitize(m.to_s, tags: [], attributes: []) }, tag.br) %>
9
+ <div id="notice" class="fixed top-4 right-4 z-50 max-w-md w-auto">
10
+ <%% flash.each do |type, msg| %>
11
+ <%% next if msg.blank? %>
12
+ <%% color = mapping[type.to_s] || :info %>
13
+ <%%= fx_alert(color: color) do %>
14
+ <%%= safe_join(Array(msg).map { |m| sanitize(m.to_s, tags: [], attributes: []) }, tag.br) %>
15
+ <%% end %>
14
16
  <%% end %>
15
- <%% end %>
17
+ </div>
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class <%= namespaced? ? "#{namespace_module}::" : "" %><%= class_name.pluralize %>Controller < ApplicationController
5
+ include Sortable
5
6
  include Pundit::Authorization
6
7
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
7
8
 
@@ -241,7 +242,7 @@ class <%= namespaced? ? "#{namespace_module}::" : "" %><%= class_name.pluralize
241
242
  private
242
243
 
243
244
  def set_<%= singular %>
244
- @<%= singular %> = <%= class_name.singularize %>.find(params[:id])
245
+ @<%= singular %> = policy_scope(<%= class_name.singularize %>).find(params[:id])
245
246
  # Note: Pundit authorization for @<%= singular %> happens in each action
246
247
  rescue ActiveRecord::RecordNotFound
247
248
  @message = t("<%= plural %>.messages.not_found")
@@ -264,7 +265,7 @@ class <%= namespaced? ? "#{namespace_module}::" : "" %><%= class_name.pluralize
264
265
 
265
266
  def set_<%= plural %>_for_bulk_actions
266
267
  <%= singular %>_ids = Array(params[:<%= singular %>_ids]).reject(&:blank?)
267
- @<%= plural %> = <%= class_name.singularize %>.where(id: <%= singular %>_ids)
268
+ @<%= plural %> = policy_scope(<%= class_name.singularize %>).where(id: <%= singular %>_ids)
268
269
 
269
270
  if @<%= plural %>.empty?
270
271
  @message = t("<%= plural %>.messages.not_selected_for_action")
@@ -0,0 +1,4 @@
1
+ en:
2
+ general:
3
+ messages:
4
+ order_error: "Invalid order parameter."
@@ -0,0 +1,23 @@
1
+ module Sortable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ helper_method :apply_sorting
6
+ end
7
+
8
+ private
9
+
10
+ def apply_sorting(resource_class, order_param, order_options = %i[created_at updated_at], default_order = { created_at: :desc })
11
+ if order_param.present?
12
+ order = order_param.rpartition("_")
13
+ if order.length == 3 && %w[asc desc].include?(order.last) && order_options.include?(order.first.to_sym)
14
+ resource_class.order(order.first.to_sym => order.last.to_sym)
15
+ else
16
+ flash[:error] = t("general.messages.order_error")
17
+ resource_class.order(default_order)
18
+ end
19
+ else
20
+ resource_class.order(default_order) # Default order
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluxbit_view_components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arthur Molina
@@ -44,9 +44,6 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 3.0.0
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: 4.0.0
50
47
  type: :runtime
51
48
  prerelease: false
52
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -54,9 +51,6 @@ dependencies:
54
51
  - - ">="
55
52
  - !ruby/object:Gem::Version
56
53
  version: 3.0.0
57
- - - "<"
58
- - !ruby/object:Gem::Version
59
- version: 4.0.0
60
54
  - !ruby/object:Gem::Dependency
61
55
  name: webdrivers
62
56
  requirement: !ruby/object:Gem::Requirement
@@ -292,15 +286,18 @@ files:
292
286
  - lib/generators/fluxbit/templates/edit.html.erb.tt
293
287
  - lib/generators/fluxbit/templates/fluxbit_pagy.css
294
288
  - lib/generators/fluxbit/templates/i18n.en.yml.tt
289
+ - lib/generators/fluxbit/templates/i18n.general.en.yml.tt
290
+ - lib/generators/fluxbit/templates/i18n.general.pt-BR.yml.tt
295
291
  - lib/generators/fluxbit/templates/i18n.pt-BR.yml.tt
296
292
  - lib/generators/fluxbit/templates/index.html.erb.tt
297
293
  - lib/generators/fluxbit/templates/index.json.jbuilder.tt
298
294
  - lib/generators/fluxbit/templates/new.html.erb.tt
299
295
  - lib/generators/fluxbit/templates/partial.html.erb.tt
300
296
  - lib/generators/fluxbit/templates/policy.rb.tt
301
- - lib/generators/fluxbit/templates/send_alert_via_drawer.erb.tt
297
+ - lib/generators/fluxbit/templates/send_alert_via_drawer.html.erb.tt
302
298
  - lib/generators/fluxbit/templates/show.html.erb.tt
303
299
  - lib/generators/fluxbit/templates/show.json.jbuilder.tt
300
+ - lib/generators/fluxbit/templates/sortable.rb.tt
304
301
  - lib/generators/fluxbit/templates/update.turbo_stream.erb.tt
305
302
  - lib/generators/fluxbit/templates/update_all.turbo_stream.erb.tt
306
303
  - lib/install/install.rb