better_ui 0.2.0 → 0.6.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/better_ui/application/main/component.html.erb +1 -1
  3. data/app/components/better_ui/application/sidebar/component.html.erb +77 -18
  4. data/app/components/better_ui/application/sidebar/component.rb +63 -5
  5. data/app/components/better_ui/general/accordion/component.html.erb +5 -0
  6. data/app/components/better_ui/general/accordion/component.rb +92 -0
  7. data/app/components/better_ui/general/accordion/item_component.html.erb +12 -0
  8. data/app/components/better_ui/general/accordion/item_component.rb +176 -0
  9. data/app/components/better_ui/general/button/component.html.erb +8 -8
  10. data/app/components/better_ui/general/button/component.rb +11 -11
  11. data/app/components/better_ui/general/dropdown/component.html.erb +21 -7
  12. data/app/components/better_ui/general/dropdown/component.rb +27 -54
  13. data/app/components/better_ui/general/dropdown/item_component.rb +2 -1
  14. data/app/components/better_ui/general/field/component.html.erb +3 -3
  15. data/app/components/better_ui/general/field/component.rb +3 -3
  16. data/app/components/better_ui/general/grid/cell_component.html.erb +3 -0
  17. data/app/components/better_ui/general/grid/cell_component.rb +390 -0
  18. data/app/components/better_ui/general/grid/component.html.erb +3 -0
  19. data/app/components/better_ui/general/grid/component.rb +301 -0
  20. data/app/components/better_ui/general/heading/component.html.erb +1 -1
  21. data/app/components/better_ui/general/icon/component.rb +2 -1
  22. data/app/components/better_ui/general/input/checkbox/component.rb +10 -10
  23. data/app/components/better_ui/general/input/pin/component.html.erb +1 -0
  24. data/app/components/better_ui/general/input/pin/component.rb +201 -0
  25. data/app/components/better_ui/general/input/radio/component.rb +10 -10
  26. data/app/components/better_ui/general/input/rating/component.html.erb +4 -0
  27. data/app/components/better_ui/general/input/rating/component.rb +272 -0
  28. data/app/components/better_ui/general/input/select/component.html.erb +76 -14
  29. data/app/components/better_ui/general/input/select/component.rb +166 -101
  30. data/app/components/better_ui/general/input/toggle/component.html.erb +5 -0
  31. data/app/components/better_ui/general/input/toggle/component.rb +242 -0
  32. data/app/components/better_ui/general/link/component.rb +1 -1
  33. data/app/components/better_ui/general/modal/component.html.erb +5 -42
  34. data/app/components/better_ui/general/modal/component.rb +22 -140
  35. data/app/components/better_ui/general/modal/modal_component.html.erb +52 -0
  36. data/app/components/better_ui/general/modal/modal_component.rb +160 -0
  37. data/app/components/better_ui/general/tabs/component.html.erb +10 -2
  38. data/app/components/better_ui/general/tabs/component.rb +26 -8
  39. data/app/components/better_ui/general/tabs/panel_component.rb +1 -1
  40. data/app/components/better_ui/general/tabs/tab_component.rb +1 -1
  41. data/app/components/better_ui/general/text/component.html.erb +1 -0
  42. data/app/components/better_ui/general/text/component.rb +194 -0
  43. data/app/helpers/better_ui/application_helper.rb +11 -4
  44. data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +73 -0
  45. data/app/helpers/better_ui/general/components/button/button_helper.rb +6 -6
  46. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +9 -0
  47. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +13 -7
  48. data/app/helpers/better_ui/general/components/field/field_helper.rb +4 -4
  49. data/app/helpers/better_ui/general/components/grid/grid_helper.rb +145 -0
  50. data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +76 -0
  51. data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +70 -0
  52. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +47 -31
  53. data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +77 -0
  54. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +34 -44
  55. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +59 -26
  56. data/app/helpers/better_ui/general/components/text/text_helper.rb +83 -0
  57. data/lib/better_ui/version.rb +1 -1
  58. data/lib/better_ui.rb +1 -0
  59. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b6dbaeb8dede2be21e4bcff93ba5a7793a08f9e84521c2b46bd8e879be6d072
4
- data.tar.gz: b9f58c90c95a748e694018c85aff6ceeb23ecee0f155b8cb45d9b64dc78da2e1
3
+ metadata.gz: d5ce58a483a9db3c7164c77c50c5206f83304c2b52f6ebff2d92dbc2ddfe0c78
4
+ data.tar.gz: 262e9f1b61ccf46254e582c2466cf4fea4c89d6ec48cb1117cb63355996a3e2c
5
5
  SHA512:
6
- metadata.gz: 7bba346a1d310e3869a232212c8708bbff5ef18f048d9361acc895efa78709b6de1e78bfee44dc3b30db77daf3cf380093331d1443b90d0bb6c18a7ad5d9e2e3
7
- data.tar.gz: be0e614d6d37d86946d7908e8f2ae0ff83a452dcfc7fc1dd29c42560f77a368bab654e556e767e2d06dcf26236281b9f39987221f0bbee451ff7a16d1125cf39
6
+ metadata.gz: 9d024e430e9eb3d69c30adc09b8cff4a8a79a3585ea0769abe0e279c7dcb54b36faf7239ad393436f5558dc0db67c29a66e7eac8f32a11232d12cffbffc2cb96
7
+ data.tar.gz: 4c18bd0f1df604a289bab875eee931283616cd22f7b1a24e239446643392228a09b92f0e9b54888b9c3d8bdc2074a089ff0d35373aca0e60bccda86ef7b843fe
@@ -1,4 +1,4 @@
1
- <main class="w-full fixed <%= get_layout_class %> <%= get_padding_class(padding) %><%= classes ? ' ' + classes : '' %>">
1
+ <main class="w-full <%= get_layout_class %> <%= get_padding_class(padding) %><%= classes ? ' ' + classes : '' %>">
2
2
  <div class="h-[calc(100vh-81px)] p-4">
3
3
  <div class="h-full w-full bg-white p-4 <%= get_padding_class(inner_padding) %> <%= get_rounded_class %> <%= get_shadow_class %> overflow-y-auto">
4
4
  <div class="h-full overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
@@ -1,10 +1,22 @@
1
- <aside class="<%= container_classes %>">
1
+ <div data-controller="bui-sidebar"
2
+ data-bui-sidebar-width-value="<%= @width == :md ? 256 : 320 %>"
3
+ data-bui-sidebar-min-width-value="200"
4
+ data-bui-sidebar-max-width-value="400"
5
+ data-bui-sidebar-pinned-value="true"
6
+ class="<%= wrapper_classes %>">
7
+
8
+ <!-- Mobile Overlay -->
9
+ <div data-bui-sidebar-target="overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
10
+
11
+ <!-- Sidebar Container -->
12
+ <aside data-bui-sidebar-target="container" class="<%= container_classes %>">
2
13
  <!-- Header Section -->
3
14
  <% if has_header? %>
4
15
  <div class="px-6 py-4 border-b border-gray-200">
5
- <% if header[:logo].present? %>
6
- <div class="flex items-center">
7
- <div class="flex-shrink-0">
16
+ <div class="flex items-center justify-between">
17
+ <% if header[:logo].present? %>
18
+ <div class="flex items-center">
19
+ <div class="flex-shrink-0">
8
20
  <% if header[:logo].is_a?(Hash) %>
9
21
  <%= bui_avatar(**header[:logo]) %>
10
22
  <% else %>
@@ -20,19 +32,30 @@
20
32
  </div>
21
33
  <% end %>
22
34
  </div>
23
- <% elsif header[:title].present? %>
24
- <div>
25
- <h2 class="text-lg font-semibold text-gray-900"><%= header[:title] %></h2>
26
- <% if header[:subtitle].present? %>
27
- <p class="text-sm text-gray-500"><%= header[:subtitle] %></p>
28
- <% end %>
29
- </div>
30
- <% end %>
35
+ <% elsif header[:title].present? %>
36
+ <div>
37
+ <h2 class="text-lg font-semibold text-gray-900"><%= header[:title] %></h2>
38
+ <% if header[:subtitle].present? %>
39
+ <p class="text-sm text-gray-500"><%= header[:subtitle] %></p>
40
+ <% end %>
41
+ </div>
42
+ <% end %>
43
+
44
+ <!-- Collapse Button (solo se collapsible) -->
45
+ <% if collapsible %>
46
+ <%= bui_button(
47
+ icon: "arrow-left",
48
+ type: :white,
49
+ size: :small,
50
+ title: "Comprimi sidebar"
51
+ ) %>
52
+ <% end %>
53
+ </div>
31
54
  </div>
32
55
  <% end %>
33
56
 
34
57
  <!-- Navigation Section -->
35
- <nav class="flex-1 px-4 py-6 space-y-6">
58
+ <nav class="flex-1 px-4 py-6 space-y-6 overflow-y-auto">
36
59
  <% navigation_sections.each do |section| %>
37
60
  <div class="space-y-2">
38
61
  <!-- Section Title -->
@@ -57,7 +80,9 @@
57
80
  <button
58
81
  type="button"
59
82
  class="group flex items-center justify-between w-full px-3 py-2 text-sm font-medium text-gray-700 rounded-md hover:bg-gray-50 hover:text-gray-900 transition-colors duration-150"
60
- data-collapse-target="#<%= item[:id] %>"
83
+ data-bui-sidebar-target="sectionTrigger"
84
+ data-bui-sidebar-section-id="<%= item[:id] %>"
85
+ data-action="click->bui-sidebar#toggleSection"
61
86
  aria-expanded="<%= item[:expanded] || false %>"
62
87
  >
63
88
  <div class="flex items-center">
@@ -68,13 +93,14 @@
68
93
  <% end %>
69
94
  <span><%= item[:label] %></span>
70
95
  </div>
71
- <span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>">
96
+ <span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>" data-bui-sidebar-chevron>
72
97
  <%= bui_icon("chevron-right", size: :small) %>
73
98
  </span>
74
99
  </button>
75
100
 
76
101
  <div
77
- id="<%= item[:id] %>"
102
+ data-bui-sidebar-target="sectionContent"
103
+ data-bui-sidebar-section-id="<%= item[:id] %>"
78
104
  class="<%= item[:expanded] ? 'block' : 'hidden' %> mt-1 space-y-1"
79
105
  >
80
106
  <% (item[:children] || []).each do |child| %>
@@ -149,7 +175,28 @@
149
175
  <!-- Footer Section -->
150
176
  <% if has_footer? %>
151
177
  <div class="px-6 py-4 border-t border-gray-200">
152
- <% if footer[:user_info].present? %>
178
+ <% if has_user_dropdown? %>
179
+ <!-- User Dropdown -->
180
+ <% user_dropdown = footer[:user_dropdown] %>
181
+ <%= bui_dropdown(
182
+ trigger: user_dropdown_trigger,
183
+ position: :top,
184
+ theme: :white,
185
+ fullwidth: true,
186
+ show_chevron: false
187
+ ) do %>
188
+ <% (user_dropdown[:menu_items] || []).each do |item| %>
189
+ <%= bui_dropdown_item(
190
+ text: item[:text],
191
+ icon: item[:icon],
192
+ href: item[:href],
193
+ theme: item[:theme] || :default,
194
+ active: item[:active] || false,
195
+ disabled: item[:disabled] || false
196
+ ) %>
197
+ <% end %>
198
+ <% end %>
199
+ <% elsif footer[:user_info].present? %>
153
200
  <div class="flex items-center">
154
201
  <% if footer[:user_info][:avatar].present? %>
155
202
  <div class="flex-shrink-0">
@@ -181,10 +228,22 @@
181
228
  <% end %>
182
229
 
183
230
  <% if footer[:content].present? %>
184
- <div class="<%= footer[:user_info].present? ? 'mt-4' : '' %>">
231
+ <div class="<%= (footer[:user_info].present? || has_user_dropdown?) ? 'mt-4' : '' %>">
185
232
  <%= footer[:content] %>
186
233
  </div>
187
234
  <% end %>
188
235
  </div>
189
236
  <% end %>
237
+
238
+ <!-- Resize Handle (solo se NON collapsible) -->
239
+ <% unless collapsible %>
240
+ <div data-bui-sidebar-target="resizeHandle"
241
+ class="absolute top-0 right-0 w-1 h-full bg-transparent hover:bg-blue-500 cursor-col-resize transition-colors duration-150 group">
242
+ <div class="absolute inset-y-0 -right-1 w-3 flex items-center justify-center">
243
+ <div class="w-0.5 h-8 bg-gray-300 group-hover:bg-blue-500 transition-colors duration-150"></div>
244
+ </div>
245
+ </div>
246
+ <% end %>
190
247
  </aside>
248
+
249
+ </div>
@@ -4,9 +4,12 @@ module BetterUi
4
4
  module Application
5
5
  module Sidebar
6
6
  class Component < ViewComponent::Base
7
- # Include degli helper per utilizzare bui_icon e bui_avatar
7
+ # Include degli helper per utilizzare bui_icon, bui_avatar, bui_button e bui_dropdown
8
8
  include BetterUi::General::Components::Icon::IconHelper
9
9
  include BetterUi::General::Components::Avatar::AvatarHelper
10
+ include BetterUi::General::Components::Button::ButtonHelper
11
+ include BetterUi::General::Components::Dropdown::DropdownHelper
12
+ include BetterUi::General::Components::Dropdown::ItemHelper
10
13
  attr_reader :width, :position, :theme, :shadow, :border, :header, :footer, :navigation_sections, :collapsible, :classes
11
14
 
12
15
  # Larghezze sidebar con classi Tailwind dirette
@@ -45,7 +48,7 @@ module BetterUi
45
48
  # @param shadow [Symbol] Tipo di ombra (:none, :sm, :md, :lg), default :lg
46
49
  # @param border [Boolean] Se mostrare il bordo destro/sinistro, default true
47
50
  # @param header [Hash] Configurazione header (logo, title, subtitle)
48
- # @param footer [Hash] Configurazione footer (content, user_info)
51
+ # @param footer [Hash] Configurazione footer (content, user_info, user_dropdown)
49
52
  # @param navigation_sections [Array] Array di sezioni di navigazione
50
53
  # @param collapsible [Boolean] Se abilitare sezioni collassabili, default true
51
54
  # @param classes [String] Classi CSS aggiuntive
@@ -73,8 +76,8 @@ module BetterUi
73
76
  @classes = classes
74
77
  end
75
78
 
76
- def container_classes
77
- base_classes = %w[fixed inset-y-0 z-50 flex flex-col overflow-y-auto]
79
+ def wrapper_classes
80
+ base_classes = %w[fixed top-0 inset-y-0 h-screen z-[9999]]
78
81
 
79
82
  # Posizione
80
83
  base_classes << (position == :right ? "right-0" : "left-0")
@@ -82,6 +85,12 @@ module BetterUi
82
85
  # Larghezza
83
86
  base_classes << width_class
84
87
 
88
+ base_classes.compact.join(" ")
89
+ end
90
+
91
+ def container_classes
92
+ base_classes = %w[flex flex-col h-full]
93
+
85
94
  # Tema
86
95
  base_classes.concat(theme_classes)
87
96
 
@@ -103,7 +112,56 @@ module BetterUi
103
112
  end
104
113
 
105
114
  def has_footer?
106
- footer.present? && (footer[:content].present? || footer[:user_info].present?)
115
+ footer.present? && (footer[:content].present? || footer[:user_info].present? || footer[:user_dropdown].present?)
116
+ end
117
+
118
+ def has_user_dropdown?
119
+ footer.present? && footer[:user_dropdown].present?
120
+ end
121
+
122
+ def user_dropdown_trigger
123
+ return '' unless has_user_dropdown?
124
+
125
+ user_dropdown = footer[:user_dropdown]
126
+ avatar_html = if user_dropdown[:avatar].present?
127
+ if user_dropdown[:avatar].is_a?(Hash)
128
+ bui_avatar(**user_dropdown[:avatar])
129
+ else
130
+ user_dropdown[:avatar].html_safe
131
+ end
132
+ else
133
+ ''
134
+ end
135
+
136
+ content_tag(:div, class: "flex items-center w-full text-left") do
137
+ avatar_section = if user_dropdown[:avatar].present?
138
+ content_tag(:div, avatar_html, class: "flex-shrink-0")
139
+ else
140
+ ''
141
+ end
142
+
143
+ text_section = content_tag(:div, class: user_dropdown[:avatar].present? ? 'ml-3 min-w-0 flex-1' : 'min-w-0 flex-1') do
144
+ name_part = if user_dropdown[:name].present?
145
+ content_tag(:p, user_dropdown[:name], class: "text-sm font-medium text-gray-700 truncate")
146
+ else
147
+ ''
148
+ end
149
+
150
+ subtitle_part = if user_dropdown[:subtitle].present?
151
+ content_tag(:p, user_dropdown[:subtitle], class: "text-xs text-gray-500 truncate")
152
+ else
153
+ ''
154
+ end
155
+
156
+ (name_part + subtitle_part).html_safe
157
+ end
158
+
159
+ chevron_section = content_tag(:div, class: "ml-auto flex-shrink-0") do
160
+ bui_icon("chevron-down", size: :small, classes: "text-gray-400")
161
+ end
162
+
163
+ (avatar_section + text_section + chevron_section).html_safe
164
+ end
107
165
  end
108
166
 
109
167
  private
@@ -0,0 +1,5 @@
1
+ <div <%= tag.attributes(wrapper_attributes) %>>
2
+ <% items.each do |item| %>
3
+ <%= item %>
4
+ <% end %>
5
+ </div>
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Accordion
6
+ class Component < ViewComponent::Base
7
+ renders_many :items, "BetterUi::General::Accordion::ItemComponent"
8
+
9
+ ACCORDION_THEME = {
10
+ default: 'border-gray-200',
11
+ white: 'border-gray-100 bg-white',
12
+ blue: 'border-blue-200',
13
+ red: 'border-red-200',
14
+ green: 'border-green-200',
15
+ yellow: 'border-yellow-200',
16
+ violet: 'border-violet-200',
17
+ orange: 'border-orange-200',
18
+ rose: 'border-rose-200'
19
+ }.freeze
20
+
21
+ ACCORDION_VARIANT = {
22
+ minimal: '',
23
+ bordered: 'border rounded-lg',
24
+ separated: 'space-y-2'
25
+ }.freeze
26
+
27
+ ACCORDION_SIZE = {
28
+ small: 'text-sm',
29
+ medium: 'text-base',
30
+ large: 'text-lg'
31
+ }.freeze
32
+
33
+ def initialize(multiple: false, theme: :default, variant: :bordered, size: :medium,
34
+ classes: '', **options)
35
+ @multiple = multiple
36
+ @theme = theme
37
+ @variant = variant
38
+ @size = size
39
+ @classes = classes
40
+ @options = options
41
+
42
+ validate_params
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :multiple, :theme, :variant, :size, :classes, :options
48
+
49
+ def validate_params
50
+ validate_theme
51
+ validate_variant
52
+ validate_size
53
+ end
54
+
55
+ def validate_theme
56
+ return if ACCORDION_THEME.key?(theme)
57
+
58
+ raise ArgumentError, "Invalid theme: #{theme}. Must be one of #{ACCORDION_THEME.keys}"
59
+ end
60
+
61
+ def validate_variant
62
+ return if ACCORDION_VARIANT.key?(variant)
63
+
64
+ raise ArgumentError, "Invalid variant: #{variant}. Must be one of #{ACCORDION_VARIANT.keys}"
65
+ end
66
+
67
+ def validate_size
68
+ return if ACCORDION_SIZE.key?(size)
69
+
70
+ raise ArgumentError, "Invalid size: #{size}. Must be one of #{ACCORDION_SIZE.keys}"
71
+ end
72
+
73
+ # Attributi per il wrapper principale
74
+ def wrapper_attributes
75
+ base_classes = [
76
+ 'bui-accordion',
77
+ ACCORDION_SIZE[size],
78
+ ACCORDION_VARIANT[variant],
79
+ ACCORDION_THEME[theme],
80
+ classes
81
+ ].compact.join(' ')
82
+
83
+ {
84
+ class: base_classes,
85
+ 'data-controller': 'bui-accordion',
86
+ 'data-bui-accordion-multiple-value': multiple
87
+ }.merge(options)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,12 @@
1
+ <div <%= tag.attributes(item_attributes) %>>
2
+ <!-- Header -->
3
+ <button <%= tag.attributes(header_attributes) %>>
4
+ <span><%= title %></span>
5
+ <i class="fas fa-<%= icon %> <%= icon_attributes[:class] %>" <%= tag.attributes(icon_attributes.except(:class)) %>></i>
6
+ </button>
7
+
8
+ <!-- Content -->
9
+ <div <%= tag.attributes(content_attributes) %>>
10
+ <%= content %>
11
+ </div>
12
+ </div>
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Accordion
6
+ class ItemComponent < ViewComponent::Base
7
+ ITEM_THEME = {
8
+ default: 'border-gray-200 bg-white',
9
+ white: 'border-gray-100 bg-white',
10
+ blue: 'border-blue-200 bg-blue-50',
11
+ red: 'border-red-200 bg-red-50',
12
+ green: 'border-green-200 bg-green-50',
13
+ yellow: 'border-yellow-200 bg-yellow-50',
14
+ violet: 'border-violet-200 bg-violet-50',
15
+ orange: 'border-orange-200 bg-orange-50',
16
+ rose: 'border-rose-200 bg-rose-50'
17
+ }.freeze
18
+
19
+ ITEM_HEADER_THEME = {
20
+ default: 'text-gray-900 hover:bg-gray-50',
21
+ white: 'text-gray-900 hover:bg-gray-50',
22
+ blue: 'text-blue-900 hover:bg-blue-100',
23
+ red: 'text-red-900 hover:bg-red-100',
24
+ green: 'text-green-900 hover:bg-green-100',
25
+ yellow: 'text-yellow-900 hover:bg-yellow-100',
26
+ violet: 'text-violet-900 hover:bg-violet-100',
27
+ orange: 'text-orange-900 hover:bg-orange-100',
28
+ rose: 'text-rose-900 hover:bg-rose-100'
29
+ }.freeze
30
+
31
+ ITEM_SIZE_HEADER = {
32
+ small: 'text-sm px-4 py-3',
33
+ medium: 'text-base px-5 py-4',
34
+ large: 'text-lg px-6 py-5'
35
+ }.freeze
36
+
37
+ ITEM_SIZE_CONTENT = {
38
+ small: 'px-4 py-3',
39
+ medium: 'px-5 py-4',
40
+ large: 'px-6 py-5'
41
+ }.freeze
42
+
43
+ ITEM_SIZE_ICON = {
44
+ small: 'w-4 h-4',
45
+ medium: 'w-5 h-5',
46
+ large: 'w-6 h-6'
47
+ }.freeze
48
+
49
+ def initialize(title:, expanded: false, disabled: false, icon: 'chevron-down',
50
+ theme: :default, size: :medium, classes: '', **options)
51
+ @title = title
52
+ @expanded = expanded
53
+ @disabled = disabled
54
+ @icon = icon
55
+ @theme = theme
56
+ @size = size
57
+ @classes = classes
58
+ @options = options
59
+
60
+ validate_params
61
+ end
62
+
63
+ private
64
+
65
+ attr_reader :title, :expanded, :disabled, :icon, :theme, :size, :classes, :options
66
+
67
+ def validate_params
68
+ validate_theme
69
+ validate_size
70
+
71
+ raise ArgumentError, 'title cannot be blank' if title.blank?
72
+ end
73
+
74
+ def validate_theme
75
+ return if ITEM_THEME.key?(theme)
76
+
77
+ raise ArgumentError, "Invalid theme: #{theme}. Must be one of #{ITEM_THEME.keys}"
78
+ end
79
+
80
+ def validate_size
81
+ return if ITEM_SIZE_HEADER.key?(size)
82
+
83
+ raise ArgumentError, "Invalid size: #{size}. Must be one of #{ITEM_SIZE_HEADER.keys}"
84
+ end
85
+
86
+ def unique_id
87
+ @unique_id ||= "accordion-item-#{SecureRandom.hex(4)}"
88
+ end
89
+
90
+ def content_id
91
+ "#{unique_id}-content"
92
+ end
93
+
94
+ def header_id
95
+ "#{unique_id}-header"
96
+ end
97
+
98
+ # Attributi per il wrapper dell'item
99
+ def item_attributes
100
+ base_classes = [
101
+ 'bui-accordion-item',
102
+ ITEM_THEME[theme],
103
+ classes
104
+ ].compact.join(' ')
105
+
106
+ {
107
+ class: base_classes,
108
+ 'data-bui-accordion-target': 'item'
109
+ }.merge(options)
110
+ end
111
+
112
+ # Attributi per il button header
113
+ def header_attributes
114
+ base_classes = [
115
+ 'bui-accordion-header',
116
+ 'w-full flex items-center justify-between text-left font-medium transition-colors duration-200',
117
+ ITEM_SIZE_HEADER[size],
118
+ ITEM_HEADER_THEME[theme],
119
+ disabled? ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
120
+ ].compact.join(' ')
121
+
122
+ {
123
+ class: base_classes,
124
+ type: 'button',
125
+ id: header_id,
126
+ 'aria-expanded': expanded,
127
+ 'aria-controls': content_id,
128
+ 'data-action': 'click->bui-accordion#toggle',
129
+ 'data-bui-accordion-target': 'trigger',
130
+ disabled: disabled
131
+ }
132
+ end
133
+
134
+ # Attributi per il contenuto
135
+ def content_attributes
136
+ base_classes = [
137
+ 'bui-accordion-content',
138
+ 'transition-all duration-300 ease-in-out overflow-hidden',
139
+ ITEM_SIZE_CONTENT[size],
140
+ expanded? ? 'block' : 'hidden'
141
+ ].compact.join(' ')
142
+
143
+ {
144
+ class: base_classes,
145
+ id: content_id,
146
+ 'aria-labelledby': header_id,
147
+ 'data-bui-accordion-target': 'content'
148
+ }
149
+ end
150
+
151
+ # Attributi per l'icona
152
+ def icon_attributes
153
+ base_classes = [
154
+ 'bui-accordion-icon',
155
+ 'transition-transform duration-200',
156
+ ITEM_SIZE_ICON[size],
157
+ expanded? ? 'rotate-180' : ''
158
+ ].compact.join(' ')
159
+
160
+ {
161
+ class: base_classes,
162
+ 'data-bui-accordion-target': 'icon'
163
+ }
164
+ end
165
+
166
+ def disabled?
167
+ disabled
168
+ end
169
+
170
+ def expanded?
171
+ expanded
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -2,15 +2,15 @@
2
2
  <% if link? %>
3
3
  <%= link_to @href, **link_attributes do %>
4
4
  <% if @icon && @icon_position == :left %>
5
- <span class="flex-shrink-0 mr-2"><%= render_icon(@icon) %></span>
5
+ <span class="flex-shrink-0<%= @text.present? ? ' mr-2' : '' %>"><%= render_icon(@icon) %></span>
6
6
  <% end %>
7
7
 
8
- <% if @label %>
9
- <span class="flex-grow"><%= @label %></span>
8
+ <% if @text %>
9
+ <span class="flex-grow"><%= @text %></span>
10
10
  <% end %>
11
11
 
12
12
  <% if @icon && @icon_position == :right %>
13
- <span class="flex-shrink-0 ml-2"><%= render_icon(@icon) %></span>
13
+ <span class="flex-shrink-0<%= @text.present? ? ' ml-2' : '' %>"><%= render_icon(@icon) %></span>
14
14
  <% end %>
15
15
 
16
16
  <%= content %>
@@ -18,15 +18,15 @@
18
18
  <% else %>
19
19
  <%= tag.button(**button_attributes) do %>
20
20
  <% if @icon && @icon_position == :left %>
21
- <span class="flex-shrink-0 mr-2"><%= render_icon(@icon) %></span>
21
+ <span class="flex-shrink-0<%= @text.present? ? ' mr-2' : '' %>"><%= render_icon(@icon) %></span>
22
22
  <% end %>
23
23
 
24
- <% if @label %>
25
- <span class="flex-grow"><%= @label %></span>
24
+ <% if @text %>
25
+ <span class="flex-grow"><%= @text %></span>
26
26
  <% end %>
27
27
 
28
28
  <% if @icon && @icon_position == :right %>
29
- <span class="flex-shrink-0 ml-2"><%= render_icon(@icon) %></span>
29
+ <span class="flex-shrink-0<%= @text.present? ? ' ml-2' : '' %>"><%= render_icon(@icon) %></span>
30
30
  <% end %>
31
31
 
32
32
  <%= content %>
@@ -2,7 +2,7 @@ module BetterUi
2
2
  module General
3
3
  module Button
4
4
  class Component < ViewComponent::Base
5
- attr_reader :label, :type, :size, :full_width, :disabled,
5
+ attr_reader :text, :theme, :size, :full_width, :disabled,
6
6
  :icon, :icon_position, :href, :method, :data, :classes, :id, :rounded, :button_type, :html_options
7
7
 
8
8
  # Classi base sempre presenti
@@ -40,8 +40,8 @@ module BetterUi
40
40
 
41
41
  # Inizializzazione del componente
42
42
  def initialize(
43
- label: nil,
44
- type: :white,
43
+ text: nil,
44
+ theme: :white,
45
45
  size: :medium,
46
46
  full_width: false,
47
47
  disabled: false,
@@ -56,8 +56,8 @@ module BetterUi
56
56
  button_type: :button,
57
57
  **html_options
58
58
  )
59
- @label = label
60
- @type = type.to_sym
59
+ @text = text
60
+ @theme = theme.to_sym
61
61
  @size = size.to_sym
62
62
  @full_width = full_width
63
63
  @disabled = disabled
@@ -95,7 +95,7 @@ module BetterUi
95
95
  end
96
96
 
97
97
  def get_button_type_classes
98
- BUTTON_THEME[@type] || BUTTON_THEME[:white]
98
+ BUTTON_THEME[@theme] || BUTTON_THEME[:white]
99
99
  end
100
100
 
101
101
  def get_border_radius_class
@@ -173,21 +173,21 @@ module BetterUi
173
173
 
174
174
  # Verifica se rendere il componente
175
175
  def render?
176
- @label.present? || @icon.present? || content.present?
176
+ @text.present? || @icon.present? || content.present?
177
177
  end
178
178
 
179
179
  private
180
180
 
181
181
  def validate_params
182
- validate_type
182
+ validate_theme
183
183
  validate_size
184
184
  validate_icon_position
185
185
  validate_rounded
186
186
  end
187
187
 
188
- def validate_type
189
- unless BUTTON_THEME.keys.include?(@type)
190
- raise ArgumentError, "Il tipo deve essere uno tra: #{BUTTON_THEME.keys.join(', ')}"
188
+ def validate_theme
189
+ unless BUTTON_THEME.keys.include?(@theme)
190
+ raise ArgumentError, "Il tema deve essere uno tra: #{BUTTON_THEME.keys.join(', ')}"
191
191
  end
192
192
  end
193
193