better_ui 0.1.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.

Potentially problematic release.


This version of better_ui might be problematic. Click here for more details.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -1
  3. data/app/assets/javascripts/better_ui/controllers/navbar_controller.js +138 -0
  4. data/app/assets/javascripts/better_ui/controllers/sidebar_controller.js +211 -0
  5. data/app/assets/javascripts/better_ui/controllers/toast_controller.js +161 -0
  6. data/app/assets/javascripts/better_ui/index.js +159 -0
  7. data/app/assets/javascripts/better_ui/toast_manager.js +77 -0
  8. data/app/assets/stylesheets/better_ui/application.css +25 -351
  9. data/app/components/better_ui/application/alert_component.html.erb +27 -0
  10. data/app/components/better_ui/application/alert_component.rb +196 -0
  11. data/app/components/better_ui/application/header_component.html.erb +88 -0
  12. data/app/components/better_ui/application/header_component.rb +188 -0
  13. data/app/components/better_ui/application/navbar_component.html.erb +294 -0
  14. data/app/components/better_ui/application/navbar_component.rb +249 -0
  15. data/app/components/better_ui/application/sidebar_component.html.erb +207 -0
  16. data/app/components/better_ui/application/sidebar_component.rb +318 -0
  17. data/app/components/better_ui/application/toast_component.html.erb +35 -0
  18. data/app/components/better_ui/application/toast_component.rb +188 -0
  19. data/app/components/better_ui/general/breadcrumb_component.html.erb +39 -0
  20. data/app/components/better_ui/general/breadcrumb_component.rb +132 -0
  21. data/app/components/better_ui/general/button_component.html.erb +34 -0
  22. data/app/components/better_ui/general/button_component.rb +193 -0
  23. data/app/components/better_ui/general/heading_component.html.erb +25 -0
  24. data/app/components/better_ui/general/heading_component.rb +142 -0
  25. data/app/components/better_ui/general/icon_component.html.erb +2 -0
  26. data/app/components/better_ui/general/icon_component.rb +101 -0
  27. data/app/components/better_ui/general/panel_component.html.erb +27 -0
  28. data/app/components/better_ui/general/panel_component.rb +97 -0
  29. data/app/components/better_ui/general/table_component.html.erb +37 -0
  30. data/app/components/better_ui/general/table_component.rb +141 -0
  31. data/app/components/better_ui/theme_helper.rb +169 -0
  32. data/app/controllers/better_ui/application_controller.rb +1 -0
  33. data/app/controllers/better_ui/docs_controller.rb +18 -25
  34. data/app/helpers/better_ui_application_helper.rb +99 -0
  35. data/app/views/layouts/component_preview.html.erb +32 -0
  36. data/config/initializers/lookbook.rb +23 -0
  37. data/config/routes.rb +6 -1
  38. data/lib/better_ui/engine.rb +24 -1
  39. data/lib/better_ui/version.rb +1 -1
  40. metadata +103 -7
  41. data/app/helpers/better_ui/application_helper.rb +0 -183
  42. data/app/views/better_ui/docs/component.html.erb +0 -365
  43. data/app/views/better_ui/docs/index.html.erb +0 -100
  44. data/app/views/better_ui/docs/show.html.erb +0 -60
  45. data/app/views/layouts/better_ui/application.html.erb +0 -135
@@ -0,0 +1,207 @@
1
+ <%# Overlay per chiudere la sidebar su mobile %>
2
+ <div data-sidebar-target="overlay" data-action="click->sidebar#close" class="<%= overlay_classes %>"></div>
3
+
4
+ <%# Container principale della sidebar %>
5
+ <div class="<%= container_classes %>" data-sidebar-target="container" <%= @data&.map { |k, v| "data-#{k}=\"#{v}\"" }&.join(' ')&.html_safe %>>
6
+ <%# Header della sidebar con titolo e pulsante di chiusura (opzionale) %>
7
+ <% if @title.present? %>
8
+ <div class="<%= header_classes %>">
9
+ <% if @title.is_a?(String) %>
10
+ <div class="flex items-center p-2">
11
+ <div class="bg-gray-900 rounded-md p-2 mr-2">
12
+ <svg class="w-5 h-5 text-white" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24">
13
+ <path fill-rule="evenodd" d="M4.5 9.75a6 6 0 0111.573-2.226 3.75 3.75 0 014.133 4.303A4.5 4.5 0 0118 20.25H6.75a5.25 5.25 0 01-2.23-10.004 6.072 6.072 0 01-.02-.496z" clip-rule="evenodd"></path>
14
+ </svg>
15
+ </div>
16
+ <div>
17
+ <h2 class="text-base font-semibold text-gray-900"><%= @title %></h2>
18
+ <p class="text-sm text-gray-500">Enterprise</p>
19
+ </div>
20
+ </div>
21
+ <% else %>
22
+ <%= @title.html_safe %>
23
+ <% end %>
24
+
25
+ <%# Pulsante per chiudere la sidebar su mobile %>
26
+ <% if @variant != :modern %>
27
+ <button
28
+ type="button"
29
+ data-action="sidebar#close"
30
+ class="md:hidden ml-auto rounded-lg p-1.5 focus:outline-none focus:ring-2 focus:ring-gray-200"
31
+ aria-label="Chiudi"
32
+ >
33
+ <svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
34
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
35
+ </svg>
36
+ </button>
37
+ <% end %>
38
+ </div>
39
+ <% end %>
40
+
41
+ <%# Menu principale della sidebar %>
42
+ <div class="flex-grow overflow-y-auto py-3">
43
+ <nav>
44
+ <%
45
+ current_section = nil
46
+ @items.each_with_index do |item, index|
47
+ %>
48
+ <% if item[:divider] %>
49
+ <hr class="<%= divider_classes %>">
50
+ <% elsif item[:heading] %>
51
+ <% current_section = item[:heading] %>
52
+ <div class="<%= section_heading_classes %> uppercase">
53
+ <%= current_section %>
54
+ </div>
55
+ <% else %>
56
+ <%
57
+ # Aggiungi automaticamente l'intestazione della sezione se è il primo elemento e non c'è un'intestazione
58
+ if index == 0 && !@items.any? { |i| i[:heading].present? }
59
+ current_section = "PLATFORM"
60
+ %>
61
+ <div class="<%= section_heading_classes %> uppercase">
62
+ <%= current_section %>
63
+ </div>
64
+ <% end %>
65
+
66
+ <div>
67
+ <% if item[:url].present? && !has_children?(item) %>
68
+ <a
69
+ href="<%= item[:url] %>"
70
+ class="<%= menu_item_classes(active_item?(item)) %>"
71
+ <% if item[:target].present? %>target="<%= item[:target] %>"<% end %>
72
+ >
73
+ <% if item[:icon].present? %>
74
+ <span class="flex-shrink-0 mr-3 text-gray-500 w-5 h-5 flex items-center justify-center">
75
+ <% if item[:icon] == "table-cells" %>
76
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
77
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h3v10H5V5zm5 0h10v2H10V5zm0 4h4v6h-4V9zm6 0h4v6h-4V9z" />
78
+ </svg>
79
+ <% elsif item[:icon] == "book" %>
80
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
81
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
82
+ </svg>
83
+ <% elsif item[:icon] == "gear" %>
84
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
85
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
86
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
87
+ </svg>
88
+ <% elsif item[:icon] == "cube" %>
89
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
90
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
91
+ </svg>
92
+ <% else %>
93
+ <%= render BetterUi::General::IconComponent.new(name: item[:icon]) %>
94
+ <% end %>
95
+ </span>
96
+ <% end %>
97
+ <span class="<%= item_label_classes %>"><%= item[:label] %></span>
98
+
99
+ <svg class="w-5 h-5 ml-auto text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
100
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
101
+ </svg>
102
+ </a>
103
+ <% else %>
104
+ <% dropdown_id = "dropdown-#{SecureRandom.hex(4)}" %>
105
+ <div class="flex flex-col">
106
+ <button
107
+ type="button"
108
+ class="<%= menu_item_classes(active_item?(item), false, has_children?(item)) %>"
109
+ data-action="sidebar#toggleSubmenu"
110
+ data-sidebar-target="dropdown"
111
+ aria-expanded="<%= active_item?(item) ? "true" : "false" %>"
112
+ aria-controls="<%= dropdown_id %>"
113
+ >
114
+ <% if item[:icon].present? %>
115
+ <span class="flex-shrink-0 mr-3 text-gray-500 w-5 h-5 flex items-center justify-center">
116
+ <% if item[:icon] == "table-cells" %>
117
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
118
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h14a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h3v10H5V5zm5 0h10v2H10V5zm0 4h4v6h-4V9zm6 0h4v6h-4V9z" />
119
+ </svg>
120
+ <% elsif item[:icon] == "book" %>
121
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
122
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
123
+ </svg>
124
+ <% elsif item[:icon] == "gear" %>
125
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
126
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
127
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
128
+ </svg>
129
+ <% elsif item[:icon] == "cube" %>
130
+ <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
131
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
132
+ </svg>
133
+ <% else %>
134
+ <%= render BetterUi::General::IconComponent.new(name: item[:icon]) %>
135
+ <% end %>
136
+ </span>
137
+ <% end %>
138
+
139
+ <span class="<%= item_label_classes %>"><%= item[:label] %></span>
140
+
141
+ <svg class="w-5 h-5 ml-auto text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" data-sidebar-target="chevron">
142
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
143
+ </svg>
144
+ </button>
145
+
146
+ <% if has_children?(item) %>
147
+ <div
148
+ id="<%= dropdown_id %>"
149
+ class="pl-5 <%= active_item?(item) ? "" : "hidden" %>"
150
+ data-sidebar-target="submenu"
151
+ >
152
+ <% item[:children].each do |child| %>
153
+ <a
154
+ href="<%= child[:url] || '#' %>"
155
+ class="block py-2.5 pr-3 text-gray-700 hover:bg-gray-50 hover:text-gray-900"
156
+ <% if child[:target].present? %>target="<%= child[:target] %>"<% end %>
157
+ >
158
+ <%= child[:label] %>
159
+ </a>
160
+ <% end %>
161
+ </div>
162
+ <% end %>
163
+ </div>
164
+ <% end %>
165
+ </div>
166
+ <% end %>
167
+ <% end %>
168
+ </nav>
169
+ </div>
170
+
171
+ <%# Footer della sidebar (opzionale) %>
172
+ <% if @footer.present? %>
173
+ <div class="<%= footer_classes %>">
174
+ <% if @footer.is_a?(String) %>
175
+ <%= @footer.html_safe %>
176
+ <% else %>
177
+ <%= @footer.html_safe %>
178
+ <% end %>
179
+ </div>
180
+ <% end %>
181
+
182
+ <%# Pulsante per il toggle della sidebar (opzionale) %>
183
+ <% if @collapsible && @variant != :modern %>
184
+ <button
185
+ type="button"
186
+ class="<%= toggle_button_classes %>"
187
+ data-action="sidebar#toggle"
188
+ data-sidebar-target="toggleButton"
189
+ aria-label="<%= @position == :left ? 'Espandi/Contrai sidebar' : 'Espandi/Contrai sidebar' %>"
190
+ >
191
+ <svg
192
+ class="w-4 h-4 transition-transform"
193
+ data-sidebar-target="toggleIcon"
194
+ aria-hidden="true"
195
+ xmlns="http://www.w3.org/2000/svg"
196
+ fill="none"
197
+ viewBox="0 0 24 24"
198
+ >
199
+ <% if @position == :left %>
200
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 19l-7-7 7-7"/>
201
+ <% else %>
202
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l7-7-7-7"/>
203
+ <% end %>
204
+ </svg>
205
+ </button>
206
+ <% end %>
207
+ </div>
@@ -0,0 +1,318 @@
1
+ module BetterUi
2
+ module Application
3
+ class SidebarComponent < ViewComponent::Base
4
+ attr_reader :title, :variant, :items, :footer, :classes, :data, :collapsible, :collapsed_default, :position, :width, :overlay_on_mobile
5
+
6
+ # Varianti di colore disponibili
7
+ VARIANTS = {
8
+ light: {
9
+ bg: "bg-white",
10
+ border: "border-gray-200",
11
+ text: "text-gray-700",
12
+ active_bg: "bg-gray-50",
13
+ active_text: "text-gray-900",
14
+ hover: "hover:bg-gray-50 hover:text-gray-900",
15
+ divider: "border-gray-200",
16
+ shadow: "shadow-sm",
17
+ icon: "text-gray-500",
18
+ chevron: "text-gray-400",
19
+ heading: "text-gray-500"
20
+ },
21
+ dark: {
22
+ bg: "bg-gray-800",
23
+ border: "border-gray-700",
24
+ text: "text-gray-200",
25
+ active_bg: "bg-gray-700",
26
+ active_text: "text-white",
27
+ hover: "hover:bg-gray-700 hover:text-white",
28
+ divider: "border-gray-700",
29
+ shadow: "shadow-lg",
30
+ icon: "text-gray-400",
31
+ chevron: "text-gray-500",
32
+ heading: "text-gray-400"
33
+ },
34
+ primary: {
35
+ bg: "bg-orange-600",
36
+ border: "border-orange-700",
37
+ text: "text-white",
38
+ active_bg: "bg-orange-700",
39
+ active_text: "text-white",
40
+ hover: "hover:bg-orange-700",
41
+ divider: "border-orange-500",
42
+ shadow: "shadow-lg",
43
+ icon: "text-orange-300",
44
+ chevron: "text-orange-400",
45
+ heading: "text-orange-200"
46
+ },
47
+ blue: {
48
+ bg: "bg-blue-800",
49
+ border: "border-blue-700",
50
+ text: "text-gray-100",
51
+ active_bg: "bg-blue-700",
52
+ active_text: "text-white",
53
+ hover: "hover:bg-blue-700 hover:text-white",
54
+ divider: "border-blue-700",
55
+ shadow: "shadow-lg",
56
+ icon: "text-blue-300",
57
+ chevron: "text-blue-400",
58
+ heading: "text-blue-200"
59
+ },
60
+ modern: {
61
+ bg: "bg-white",
62
+ border: "border-gray-200",
63
+ text: "text-gray-700",
64
+ active_bg: "bg-gray-100",
65
+ active_text: "text-gray-900",
66
+ hover: "hover:bg-gray-50",
67
+ divider: "border-gray-100",
68
+ shadow: "shadow-none",
69
+ icon: "text-gray-500",
70
+ chevron: "text-gray-400",
71
+ heading: "text-gray-500 uppercase text-xs font-medium"
72
+ }
73
+ }
74
+
75
+ # Dimensioni disponibili per la sidebar
76
+ WIDTHS = {
77
+ narrow: "w-60",
78
+ medium: "w-64",
79
+ wide: "w-72",
80
+ custom: "" # La larghezza custom viene specificata nelle classi aggiuntive
81
+ }
82
+
83
+ # Posizioni disponibili
84
+ POSITIONS = {
85
+ left: "left-0",
86
+ right: "right-0"
87
+ }
88
+
89
+ # Inizializzazione del componente
90
+ def initialize(
91
+ title: nil,
92
+ variant: :modern,
93
+ items: [],
94
+ footer: nil,
95
+ classes: nil,
96
+ data: {},
97
+ collapsible: true,
98
+ collapsed_default: false,
99
+ position: :left,
100
+ width: :narrow,
101
+ overlay_on_mobile: true
102
+ )
103
+ @title = title
104
+ @variant = variant.to_sym
105
+ @items = items || []
106
+ @footer = footer
107
+ @classes = classes
108
+ @data = data || {}
109
+ @collapsible = collapsible
110
+ @collapsed_default = collapsed_default
111
+ @position = position.to_sym
112
+ @width = width.to_sym
113
+ @overlay_on_mobile = overlay_on_mobile
114
+
115
+ # Aggiungiamo controller Stimulus per la gestione della sidebar
116
+ @data[:controller] = "sidebar" if @data[:controller].blank?
117
+ @data[:sidebar_position_value] = @position.to_s
118
+ @data[:sidebar_collapsed_value] = @collapsed_default.to_s
119
+ @data[:sidebar_overlay_on_mobile_value] = @overlay_on_mobile.to_s
120
+ end
121
+
122
+ # Genera le classi per il container della sidebar
123
+ def container_classes
124
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
125
+ width_class = WIDTHS.fetch(@width, WIDTHS[:narrow])
126
+ position_class = POSITIONS.fetch(@position, POSITIONS[:left])
127
+
128
+ [
129
+ "h-screen relative z-40",
130
+ styles[:bg],
131
+ styles[:border],
132
+ @position == :left ? "border-r" : "border-l",
133
+ styles[:shadow],
134
+ width_class,
135
+ position_class,
136
+ @classes,
137
+ "transition-transform duration-300 ease-in-out transform",
138
+ "flex flex-col"
139
+ ].compact.join(" ")
140
+ end
141
+
142
+ # Genera le classi per l'overlay
143
+ def overlay_classes
144
+ [
145
+ "fixed inset-0 bg-black bg-opacity-50 z-30",
146
+ "transition-opacity duration-300 ease-in-out",
147
+ "hidden"
148
+ ].join(" ")
149
+ end
150
+
151
+ # Genera le classi per il pulsante del toggle
152
+ def toggle_button_classes
153
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
154
+ position_class = @position == :left ? "right-0 -mr-3" : "left-0 -ml-3"
155
+
156
+ [
157
+ "absolute top-16",
158
+ position_class,
159
+ "z-50 flex items-center justify-center",
160
+ "w-6 h-12 rounded-lg",
161
+ styles[:bg],
162
+ styles[:border],
163
+ "cursor-pointer shadow-lg",
164
+ "transform transition-transform duration-300 ease-in-out"
165
+ ].compact.join(" ")
166
+ end
167
+
168
+ # Genera le classi per l'intestazione
169
+ def header_classes
170
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
171
+
172
+ [
173
+ "px-4 py-4 flex items-center",
174
+ @variant == :modern ? "" : "border-b #{styles[:border]}"
175
+ ].compact.join(" ")
176
+ end
177
+
178
+ # Genera le classi per i link nel menu
179
+ def menu_item_classes(active = false, nested = false, has_children = false)
180
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
181
+
182
+ padding_x = @variant == :modern ? "pl-5 pr-3" : "px-3"
183
+ padding_y = @variant == :modern ? "py-2.5" : "py-2"
184
+ rounded = @variant == :modern ? "" : "rounded-md"
185
+
186
+ base_classes = [
187
+ "flex items-center #{padding_y} #{padding_x} #{rounded} my-0.5 w-full",
188
+ ]
189
+
190
+ if active
191
+ active_classes = @variant == :modern ? "bg-gray-100 font-medium" : styles[:active_bg]
192
+ [
193
+ *base_classes,
194
+ active_classes,
195
+ styles[:active_text],
196
+ ].join(" ")
197
+ else
198
+ [
199
+ *base_classes,
200
+ styles[:text],
201
+ styles[:hover],
202
+ has_children ? "cursor-pointer" : ""
203
+ ].join(" ")
204
+ end
205
+ end
206
+
207
+ # Genera le classi per i separatori
208
+ def divider_classes
209
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
210
+
211
+ [
212
+ "my-2 border-t",
213
+ styles[:divider]
214
+ ].join(" ")
215
+ end
216
+
217
+ # Genera le classi per il footer
218
+ def footer_classes
219
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
220
+
221
+ [
222
+ "mt-auto px-4 py-3",
223
+ @variant == :modern ? "" : "border-t #{styles[:border]}"
224
+ ].join(" ")
225
+ end
226
+
227
+ # Genera le classi per le icone
228
+ def icon_classes
229
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
230
+
231
+ [
232
+ "mr-3 flex-shrink-0",
233
+ styles[:icon]
234
+ ].join(" ")
235
+ end
236
+
237
+ # Genera le classi per la label della voce di menu
238
+ def item_label_classes
239
+ [
240
+ "flex-1 truncate"
241
+ ].join(" ")
242
+ end
243
+
244
+ # Genera le classi per il badge nella voce di menu
245
+ def badge_classes(type = :default)
246
+ badge_types = {
247
+ default: "bg-gray-200 text-gray-800",
248
+ primary: "bg-orange-100 text-orange-800",
249
+ success: "bg-green-100 text-green-800",
250
+ warning: "bg-yellow-100 text-yellow-800",
251
+ danger: "bg-red-100 text-red-800",
252
+ info: "bg-blue-100 text-blue-800"
253
+ }
254
+
255
+ [
256
+ "px-2 py-0.5 text-xs rounded-full",
257
+ badge_types.fetch(type.to_sym, badge_types[:default])
258
+ ].join(" ")
259
+ end
260
+
261
+ # Genera le classi per il titolo di sezione
262
+ def section_heading_classes
263
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
264
+
265
+ if @variant == :modern
266
+ "px-5 py-2 text-xs font-medium text-gray-500"
267
+ else
268
+ [
269
+ "px-4 py-2 text-xs tracking-wider",
270
+ styles[:heading]
271
+ ].join(" ")
272
+ end
273
+ end
274
+
275
+ # Genera le classi per icona chevron
276
+ def chevron_classes
277
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
278
+
279
+ [
280
+ "ml-auto",
281
+ styles[:chevron]
282
+ ].join(" ")
283
+ end
284
+
285
+ # Verifica se la sidebar deve essere resa
286
+ def render?
287
+ true
288
+ end
289
+
290
+ # Determina se un elemento dovrebbe essere considerato attivo
291
+ def active_item?(item)
292
+ item[:active] == true
293
+ end
294
+
295
+ # Verifica se un elemento ha figli
296
+ def has_children?(item)
297
+ item[:children].present? && item[:children].is_a?(Array) && item[:children].any?
298
+ end
299
+
300
+ # Verifica se un elemento ha un badge
301
+ def has_badge?(item)
302
+ item[:badge].present?
303
+ end
304
+
305
+ # Ottiene il tipo di badge per un elemento
306
+ def badge_type(item)
307
+ return :default unless item[:badge].is_a?(Hash)
308
+ (item[:badge][:type] || :default).to_sym
309
+ end
310
+
311
+ # Ottiene il testo del badge per un elemento
312
+ def badge_text(item)
313
+ return item[:badge].to_s unless item[:badge].is_a?(Hash)
314
+ item[:badge][:text].to_s
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,35 @@
1
+ <div role="status" aria-live="polite" class="<%= container_classes %>"
2
+ <%= @data&.map { |k, v| "data-#{k}=\"#{v}\"" }&.join(' ')&.html_safe %>>
3
+
4
+ <% if effective_icon.present? %>
5
+ <div class="<%= icon_classes %>">
6
+ <%= render BetterUi::General::IconComponent.new(name: effective_icon) %>
7
+ </div>
8
+ <% end %>
9
+
10
+ <div class="<%= content_classes %>">
11
+ <% if @title.present? %>
12
+ <div class="<%= title_classes %>"><%= @title %></div>
13
+ <% end %>
14
+
15
+ <% if @message.present? %>
16
+ <div class="<%= message_classes %>"><%= @message %></div>
17
+ <% end %>
18
+
19
+ <% if content.present? %>
20
+ <div class="<%= message_classes %>"><%= content %></div>
21
+ <% end %>
22
+
23
+ <% if @auto_hide %>
24
+ <div class="w-full bg-gray-200 h-1 mt-2 rounded overflow-hidden">
25
+ <div class="bg-current h-1 transition-all" data-toast-target="progressBar"></div>
26
+ </div>
27
+ <% end %>
28
+ </div>
29
+
30
+ <% if @dismissible %>
31
+ <button type="button" class="<%= close_button_classes %>" data-action="toast#hide" aria-label="Chiudi">
32
+ <%= render BetterUi::General::IconComponent.new(name: "xmark") %>
33
+ </button>
34
+ <% end %>
35
+ </div>