better_ui 0.1.0 → 0.2.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +225 -119
  4. data/app/assets/stylesheets/better_ui/application.css +0 -356
  5. data/app/components/better_ui/application/card/component.html.erb +20 -0
  6. data/app/components/better_ui/application/card/component.rb +214 -0
  7. data/app/components/better_ui/application/main/component.html.erb +9 -0
  8. data/app/components/better_ui/application/main/component.rb +123 -0
  9. data/app/components/better_ui/application/navbar/component.html.erb +92 -0
  10. data/app/components/better_ui/application/navbar/component.rb +136 -0
  11. data/app/components/better_ui/application/sidebar/component.html.erb +190 -0
  12. data/app/components/better_ui/application/sidebar/component.rb +129 -0
  13. data/app/components/better_ui/general/alert/component.html.erb +32 -0
  14. data/app/components/better_ui/general/alert/component.rb +242 -0
  15. data/app/components/better_ui/general/avatar/component.html.erb +20 -0
  16. data/app/components/better_ui/general/avatar/component.rb +301 -0
  17. data/app/components/better_ui/general/badge/component.html.erb +23 -0
  18. data/app/components/better_ui/general/badge/component.rb +248 -0
  19. data/app/components/better_ui/general/breadcrumb/component.html.erb +15 -0
  20. data/app/components/better_ui/general/breadcrumb/component.rb +187 -0
  21. data/app/components/better_ui/general/button/component.html.erb +34 -0
  22. data/app/components/better_ui/general/button/component.rb +214 -0
  23. data/app/components/better_ui/general/divider/component.html.erb +10 -0
  24. data/app/components/better_ui/general/divider/component.rb +226 -0
  25. data/app/components/better_ui/general/dropdown/component.html.erb +14 -0
  26. data/app/components/better_ui/general/dropdown/component.rb +219 -0
  27. data/app/components/better_ui/general/dropdown/divider_component.html.erb +1 -0
  28. data/app/components/better_ui/general/dropdown/divider_component.rb +41 -0
  29. data/app/components/better_ui/general/dropdown/item_component.html.erb +6 -0
  30. data/app/components/better_ui/general/dropdown/item_component.rb +118 -0
  31. data/app/components/better_ui/general/field/component.html.erb +27 -0
  32. data/app/components/better_ui/general/field/component.rb +37 -0
  33. data/app/components/better_ui/general/heading/component.html.erb +22 -0
  34. data/app/components/better_ui/general/heading/component.rb +257 -0
  35. data/app/components/better_ui/general/icon/component.html.erb +7 -0
  36. data/app/components/better_ui/general/icon/component.rb +239 -0
  37. data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
  38. data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
  39. data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
  40. data/app/components/better_ui/general/input/datetime/component.rb +223 -0
  41. data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
  42. data/app/components/better_ui/general/input/radio/component.rb +230 -0
  43. data/app/components/better_ui/general/input/select/component.html.erb +16 -0
  44. data/app/components/better_ui/general/input/select/component.rb +184 -0
  45. data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
  46. data/app/components/better_ui/general/input/select/select_component.rb +37 -0
  47. data/app/components/better_ui/general/input/text/component.html.erb +5 -0
  48. data/app/components/better_ui/general/input/text/component.rb +171 -0
  49. data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
  50. data/app/components/better_ui/general/input/textarea/component.rb +166 -0
  51. data/app/components/better_ui/general/link/component.html.erb +18 -0
  52. data/app/components/better_ui/general/link/component.rb +258 -0
  53. data/app/components/better_ui/general/modal/component.html.erb +42 -0
  54. data/app/components/better_ui/general/modal/component.rb +165 -0
  55. data/app/components/better_ui/general/pagination/component.html.erb +85 -0
  56. data/app/components/better_ui/general/pagination/component.rb +216 -0
  57. data/app/components/better_ui/general/panel/component.html.erb +28 -0
  58. data/app/components/better_ui/general/panel/component.rb +249 -0
  59. data/app/components/better_ui/general/progress/component.html.erb +11 -0
  60. data/app/components/better_ui/general/progress/component.rb +160 -0
  61. data/app/components/better_ui/general/spinner/component.html.erb +35 -0
  62. data/app/components/better_ui/general/spinner/component.rb +93 -0
  63. data/app/components/better_ui/general/table/component.html.erb +5 -0
  64. data/app/components/better_ui/general/table/component.rb +217 -0
  65. data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
  66. data/app/components/better_ui/general/table/tbody_component.rb +30 -0
  67. data/app/components/better_ui/general/table/td_component.html.erb +3 -0
  68. data/app/components/better_ui/general/table/td_component.rb +44 -0
  69. data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
  70. data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
  71. data/app/components/better_ui/general/table/th_component.html.erb +6 -0
  72. data/app/components/better_ui/general/table/th_component.rb +51 -0
  73. data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
  74. data/app/components/better_ui/general/table/thead_component.rb +28 -0
  75. data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
  76. data/app/components/better_ui/general/table/tr_component.rb +30 -0
  77. data/app/components/better_ui/general/tabs/component.html.erb +3 -0
  78. data/app/components/better_ui/general/tabs/component.rb +102 -0
  79. data/app/components/better_ui/general/tabs/panel_component.html.erb +3 -0
  80. data/app/components/better_ui/general/tabs/panel_component.rb +37 -0
  81. data/app/components/better_ui/general/tabs/tab_component.html.erb +13 -0
  82. data/app/components/better_ui/general/tabs/tab_component.rb +111 -0
  83. data/app/components/better_ui/general/tag/component.html.erb +3 -0
  84. data/app/components/better_ui/general/tag/component.rb +104 -0
  85. data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
  86. data/app/components/better_ui/general/tooltip/component.rb +239 -0
  87. data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
  88. data/app/helpers/better_ui/application/components/card.rb +11 -0
  89. data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
  90. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
  91. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
  92. data/app/helpers/better_ui/application_helper.rb +51 -179
  93. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
  94. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
  95. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
  96. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
  97. data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
  98. data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
  99. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
  100. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +32 -0
  101. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +79 -0
  102. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +62 -0
  103. data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
  104. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
  105. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
  106. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
  107. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
  108. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
  109. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
  110. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
  111. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
  112. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
  113. data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
  114. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +95 -0
  115. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +82 -0
  116. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
  117. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
  118. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
  119. data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
  120. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
  121. data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
  122. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
  123. data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
  124. data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
  125. data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
  126. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +62 -0
  127. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +55 -0
  128. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +62 -0
  129. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
  130. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
  131. data/app/views/layouts/better_ui/application.html.erb +6 -124
  132. data/config/initializers/lookbook.rb +23 -0
  133. data/config/routes.rb +0 -8
  134. data/lib/better_ui/engine.rb +5 -19
  135. data/lib/better_ui/railtie.rb +20 -0
  136. data/lib/better_ui/version.rb +1 -1
  137. data/lib/better_ui.rb +4 -20
  138. metadata +155 -28
  139. data/app/controllers/better_ui/docs_controller.rb +0 -41
  140. data/app/views/better_ui/docs/component.html.erb +0 -365
  141. data/app/views/better_ui/docs/index.html.erb +0 -100
  142. data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Pagination
6
+ class Component < ViewComponent::Base
7
+ include BetterUi::Engine.helpers
8
+
9
+ # Costanti per temi
10
+ PAGINATION_THEME = {
11
+ default: {
12
+ container: 'border-gray-300',
13
+ page: 'border-gray-300 text-gray-500 hover:bg-gray-50 hover:text-gray-700',
14
+ current: 'border-blue-500 bg-blue-50 text-blue-600',
15
+ disabled: 'border-gray-300 text-gray-300 cursor-not-allowed'
16
+ },
17
+ blue: {
18
+ container: 'border-blue-300',
19
+ page: 'border-blue-300 text-blue-600 hover:bg-blue-50 hover:text-blue-700',
20
+ current: 'border-blue-500 bg-blue-100 text-blue-700',
21
+ disabled: 'border-blue-200 text-blue-300 cursor-not-allowed'
22
+ },
23
+ red: {
24
+ container: 'border-red-300',
25
+ page: 'border-red-300 text-red-600 hover:bg-red-50 hover:text-red-700',
26
+ current: 'border-red-500 bg-red-100 text-red-700',
27
+ disabled: 'border-red-200 text-red-300 cursor-not-allowed'
28
+ },
29
+ green: {
30
+ container: 'border-green-300',
31
+ page: 'border-green-300 text-green-600 hover:bg-green-50 hover:text-green-700',
32
+ current: 'border-green-500 bg-green-100 text-green-700',
33
+ disabled: 'border-green-200 text-green-300 cursor-not-allowed'
34
+ },
35
+ yellow: {
36
+ container: 'border-yellow-300',
37
+ page: 'border-yellow-300 text-yellow-600 hover:bg-yellow-50 hover:text-yellow-700',
38
+ current: 'border-yellow-500 bg-yellow-100 text-yellow-700',
39
+ disabled: 'border-yellow-200 text-yellow-300 cursor-not-allowed'
40
+ },
41
+ violet: {
42
+ container: 'border-violet-300',
43
+ page: 'border-violet-300 text-violet-600 hover:bg-violet-50 hover:text-violet-700',
44
+ current: 'border-violet-500 bg-violet-100 text-violet-700',
45
+ disabled: 'border-violet-200 text-violet-300 cursor-not-allowed'
46
+ },
47
+ orange: {
48
+ container: 'border-orange-300',
49
+ page: 'border-orange-300 text-orange-600 hover:bg-orange-50 hover:text-orange-700',
50
+ current: 'border-orange-500 bg-orange-100 text-orange-700',
51
+ disabled: 'border-orange-200 text-orange-300 cursor-not-allowed'
52
+ },
53
+ rose: {
54
+ container: 'border-rose-300',
55
+ page: 'border-rose-300 text-rose-600 hover:bg-rose-50 hover:text-rose-700',
56
+ current: 'border-rose-500 bg-rose-100 text-rose-700',
57
+ disabled: 'border-rose-200 text-rose-300 cursor-not-allowed'
58
+ },
59
+ white: {
60
+ container: 'border-white',
61
+ page: 'border-white text-gray-700 hover:bg-white hover:text-gray-900',
62
+ current: 'border-white bg-white text-gray-900',
63
+ disabled: 'border-white text-gray-400 cursor-not-allowed'
64
+ }
65
+ }.freeze
66
+
67
+ # Costanti per dimensioni
68
+ PAGINATION_SIZE = {
69
+ small: 'px-2 py-1 text-sm',
70
+ medium: 'px-3 py-2 text-base',
71
+ large: 'px-4 py-3 text-lg'
72
+ }.freeze
73
+
74
+ def initialize(current_page:, total_pages:, path:, theme: :default, size: :medium,
75
+ window: 2, show_info: false, per_page: nil, classes: '', **options)
76
+ @current_page = current_page.to_i
77
+ @total_pages = total_pages.to_i
78
+ @path = path
79
+ @theme = theme
80
+ @size = size
81
+ @window = window.to_i
82
+ @show_info = show_info
83
+ @per_page = per_page
84
+ @classes = classes
85
+ @options = options
86
+
87
+ validate_params
88
+ end
89
+
90
+ private
91
+
92
+ attr_reader :current_page, :total_pages, :path, :theme, :size, :window,
93
+ :show_info, :per_page, :classes, :options
94
+
95
+ def validate_params
96
+ validate_theme
97
+ validate_size
98
+ validate_pages
99
+ end
100
+
101
+ def validate_theme
102
+ return if PAGINATION_THEME.key?(theme)
103
+
104
+ raise ArgumentError,
105
+ "Invalid theme: #{theme}. Valid themes are: #{PAGINATION_THEME.keys.join(', ')}"
106
+ end
107
+
108
+ def validate_size
109
+ return if PAGINATION_SIZE.key?(size)
110
+
111
+ raise ArgumentError,
112
+ "Invalid size: #{size}. Valid sizes are: #{PAGINATION_SIZE.keys.join(', ')}"
113
+ end
114
+
115
+ def validate_pages
116
+ if current_page < 1 || current_page > total_pages
117
+ raise ArgumentError, "current_page must be between 1 and #{total_pages}"
118
+ end
119
+
120
+ if total_pages < 1
121
+ raise ArgumentError, "total_pages must be at least 1"
122
+ end
123
+ end
124
+
125
+ def theme_classes
126
+ PAGINATION_THEME[theme]
127
+ end
128
+
129
+ def size_classes
130
+ PAGINATION_SIZE[size]
131
+ end
132
+
133
+ def container_classes
134
+ "inline-flex -space-x-px rounded-md shadow-sm #{theme_classes[:container]} #{classes}".strip
135
+ end
136
+
137
+ def page_link_classes(page_num)
138
+ base_classes = "relative inline-flex items-center border #{size_classes} font-medium"
139
+
140
+ if page_num == current_page
141
+ "#{base_classes} #{theme_classes[:current]}"
142
+ else
143
+ "#{base_classes} #{theme_classes[:page]} focus:z-10 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
144
+ end
145
+ end
146
+
147
+ def disabled_classes
148
+ "relative inline-flex items-center border #{size_classes} font-medium #{theme_classes[:disabled]}"
149
+ end
150
+
151
+ def page_range
152
+ return [1] if total_pages == 1
153
+
154
+ start_page = [current_page - window, 1].max
155
+ end_page = [current_page + window, total_pages].min
156
+
157
+ # Espandi il range se possibile
158
+ if end_page - start_page < (window * 2)
159
+ if start_page == 1
160
+ end_page = [start_page + (window * 2), total_pages].min
161
+ elsif end_page == total_pages
162
+ start_page = [end_page - (window * 2), 1].max
163
+ end
164
+ end
165
+
166
+ (start_page..end_page).to_a
167
+ end
168
+
169
+ def show_left_ellipsis?
170
+ page_range.first > 2
171
+ end
172
+
173
+ def show_right_ellipsis?
174
+ page_range.last < total_pages - 1
175
+ end
176
+
177
+ def build_url(page_num)
178
+ return '#' if page_num == current_page
179
+
180
+ uri = URI.parse(path)
181
+ params = URI.decode_www_form(uri.query || '')
182
+ params = params.reject { |k, _v| k == 'page' }
183
+ params << ['page', page_num] if page_num > 1
184
+
185
+ uri.query = params.empty? ? nil : URI.encode_www_form(params)
186
+ uri.to_s
187
+ end
188
+
189
+ def previous_page
190
+ current_page > 1 ? current_page - 1 : nil
191
+ end
192
+
193
+ def next_page
194
+ current_page < total_pages ? current_page + 1 : nil
195
+ end
196
+
197
+ def info_text
198
+ return '' unless show_info && per_page
199
+
200
+ start_item = ((current_page - 1) * per_page) + 1
201
+ end_item = [current_page * per_page, total_items].min
202
+
203
+ "Mostrando #{start_item}-#{end_item} di #{total_items} risultati"
204
+ end
205
+
206
+ def total_items
207
+ total_pages * per_page
208
+ end
209
+
210
+ def should_show_pagination?
211
+ total_pages > 1
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,28 @@
1
+ <%# Template per il panel %>
2
+ <div <%= tag.attributes(panel_attributes) %>>
3
+ <% if show_header? %>
4
+ <div class="<%= header_classes %>">
5
+ <% if @header.present? %>
6
+ <%= raw @header %>
7
+ <% elsif @title.present? %>
8
+ <div class="<%= title_classes %>"><%= @title %></div>
9
+ <% end %>
10
+ </div>
11
+ <% end %>
12
+
13
+ <% if show_body? %>
14
+ <div class="<%= body_classes %>">
15
+ <% if @body.present? %>
16
+ <%= raw @body %>
17
+ <% elsif content.present? %>
18
+ <%= content %>
19
+ <% end %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <% if show_footer? %>
24
+ <div class="<%= footer_classes %>">
25
+ <%= raw @footer %>
26
+ </div>
27
+ <% end %>
28
+ </div>
@@ -0,0 +1,249 @@
1
+ module BetterUi
2
+ module General
3
+ module Panel
4
+ class Component < ViewComponent::Base
5
+ attr_reader :header, :footer, :body, :title, :padding, :theme, :style, :radius
6
+
7
+ # Classi base sempre presenti
8
+ PANEL_BASE_CLASSES = "overflow-hidden"
9
+
10
+ # Temi con classi Tailwind dirette - LOGICA CORRETTA
11
+ PANEL_THEME_CLASSES = {
12
+ default: "bg-gray-800 border-gray-700", # Scuro per sfondi scuri
13
+ white: "bg-white border-gray-200", # Chiaro per sfondi chiari
14
+ red: "bg-red-50 border-red-200",
15
+ rose: "bg-rose-50 border-rose-200",
16
+ orange: "bg-orange-50 border-orange-200",
17
+ green: "bg-green-50 border-green-200",
18
+ blue: "bg-blue-50 border-blue-200",
19
+ yellow: "bg-yellow-50 border-yellow-200",
20
+ violet: "bg-violet-50 border-violet-200"
21
+ }
22
+
23
+ # Temi per testo - LOGICA CORRETTA
24
+ PANEL_TEXT_THEME_CLASSES = {
25
+ default: "text-white", # Testo bianco per sfondi scuri
26
+ white: "text-gray-900", # Testo nero per sfondi chiari
27
+ red: "text-red-900",
28
+ rose: "text-rose-900",
29
+ orange: "text-orange-900",
30
+ green: "text-green-900",
31
+ blue: "text-blue-900",
32
+ yellow: "text-yellow-900",
33
+ violet: "text-violet-900"
34
+ }
35
+
36
+ # Stili con classi Tailwind dirette
37
+ PANEL_STYLE_CLASSES = {
38
+ default: "border shadow-sm",
39
+ flat: "border-0",
40
+ raised: "border shadow-lg",
41
+ bordered: "border-2"
42
+ }
43
+
44
+ # Padding con classi Tailwind dirette
45
+ PANEL_PADDING_CLASSES = {
46
+ none: "p-0",
47
+ small: "p-2",
48
+ medium: "p-4",
49
+ large: "p-6"
50
+ }
51
+
52
+ # Radius con classi Tailwind dirette
53
+ PANEL_RADIUS_CLASSES = {
54
+ none: "rounded-none",
55
+ small: "rounded",
56
+ medium: "rounded-md",
57
+ large: "rounded-lg",
58
+ full: "rounded-full"
59
+ }
60
+
61
+ # @param title [String] titolo del pannello (opzionale)
62
+ # @param body [String] contenuto HTML del pannello (opzionale)
63
+ # @param header [String] header personalizzato (opzionale)
64
+ # @param footer [String] footer del pannello (opzionale)
65
+ # @param theme [Symbol] tema del colore (:default, :white, etc.)
66
+ # @param style [Symbol] stile (:default, :flat, :raised, :bordered)
67
+ # @param padding [Symbol] padding interno (:none, :small, :medium, :large)
68
+ # @param radius [Symbol] raggio dei bordi (:none, :small, :medium, :large, :full)
69
+ # @param html_options [Hash] opzioni HTML aggiuntive
70
+ def initialize(
71
+ title: nil,
72
+ body: nil,
73
+ header: nil,
74
+ footer: nil,
75
+ theme: :white,
76
+ style: :default,
77
+ padding: :medium,
78
+ radius: :small,
79
+ **html_options
80
+ )
81
+ @title = title
82
+ @body = body
83
+ @header = header
84
+ @footer = footer
85
+ @theme = theme.to_sym
86
+ @style = style.to_sym
87
+ @padding = padding.to_sym
88
+ @radius = radius.to_sym
89
+ @html_options = html_options
90
+
91
+ validate_params
92
+ end
93
+
94
+ # Combina tutte le classi CSS per il panel
95
+ def combined_classes
96
+ [
97
+ PANEL_BASE_CLASSES,
98
+ get_theme_class,
99
+ get_style_class,
100
+ get_radius_class,
101
+ @html_options[:class]
102
+ ].compact.join(" ")
103
+ end
104
+
105
+ # Restituisce gli attributi HTML per il panel
106
+ def panel_attributes
107
+ attrs = @html_options.except(:class)
108
+ attrs[:class] = combined_classes
109
+ attrs
110
+ end
111
+
112
+ # Classi per l'header
113
+ def header_classes
114
+ [
115
+ "border-b",
116
+ get_border_theme_class,
117
+ get_text_theme_class,
118
+ get_padding_class
119
+ ].compact.join(" ")
120
+ end
121
+
122
+ # Classi per il body
123
+ def body_classes
124
+ [
125
+ get_text_theme_class,
126
+ get_padding_class
127
+ ].compact.join(" ")
128
+ end
129
+
130
+ # Classi per il footer
131
+ def footer_classes
132
+ [
133
+ "border-t",
134
+ get_border_theme_class,
135
+ get_text_theme_class,
136
+ get_padding_class
137
+ ].compact.join(" ")
138
+ end
139
+
140
+ # Classi per il title
141
+ def title_classes
142
+ [
143
+ "font-semibold text-lg leading-6",
144
+ get_text_theme_class
145
+ ].compact.join(" ")
146
+ end
147
+
148
+ # Determina se il pannello deve essere renderizzato
149
+ def render?
150
+ @body.present? || @header.present? || @footer.present? || content.present?
151
+ end
152
+
153
+ # Determina se mostrare l'header
154
+ def show_header?
155
+ @header.present? || @title.present?
156
+ end
157
+
158
+ # Determina se mostrare il body
159
+ def show_body?
160
+ @body.present? || content.present?
161
+ end
162
+
163
+ # Determina se mostrare il footer
164
+ def show_footer?
165
+ @footer.present?
166
+ end
167
+
168
+ private
169
+
170
+ def get_theme_class
171
+ PANEL_THEME_CLASSES[@theme] || PANEL_THEME_CLASSES[:white]
172
+ end
173
+
174
+ def get_text_theme_class
175
+ PANEL_TEXT_THEME_CLASSES[@theme] || PANEL_TEXT_THEME_CLASSES[:white]
176
+ end
177
+
178
+ def get_border_theme_class
179
+ # Usa lo stesso colore del bordo principale ma più leggero per i separatori interni
180
+ case @theme
181
+ when :default
182
+ "border-gray-600"
183
+ when :white
184
+ "border-gray-100"
185
+ when :red
186
+ "border-red-100"
187
+ when :rose
188
+ "border-rose-100"
189
+ when :orange
190
+ "border-orange-100"
191
+ when :green
192
+ "border-green-100"
193
+ when :blue
194
+ "border-blue-100"
195
+ when :yellow
196
+ "border-yellow-100"
197
+ when :violet
198
+ "border-violet-100"
199
+ else
200
+ "border-gray-100"
201
+ end
202
+ end
203
+
204
+ def get_style_class
205
+ PANEL_STYLE_CLASSES[@style] || PANEL_STYLE_CLASSES[:default]
206
+ end
207
+
208
+ def get_radius_class
209
+ PANEL_RADIUS_CLASSES[@radius] || PANEL_RADIUS_CLASSES[:small]
210
+ end
211
+
212
+ def get_padding_class
213
+ PANEL_PADDING_CLASSES[@padding] || PANEL_PADDING_CLASSES[:medium]
214
+ end
215
+
216
+ def validate_params
217
+ validate_theme
218
+ validate_style
219
+ validate_padding
220
+ validate_radius
221
+ end
222
+
223
+ def validate_theme
224
+ unless PANEL_THEME_CLASSES.keys.include?(@theme)
225
+ raise ArgumentError, "Il tema deve essere uno tra: #{PANEL_THEME_CLASSES.keys.join(', ')}"
226
+ end
227
+ end
228
+
229
+ def validate_style
230
+ unless PANEL_STYLE_CLASSES.keys.include?(@style)
231
+ raise ArgumentError, "Lo stile deve essere uno tra: #{PANEL_STYLE_CLASSES.keys.join(', ')}"
232
+ end
233
+ end
234
+
235
+ def validate_padding
236
+ unless PANEL_PADDING_CLASSES.keys.include?(@padding)
237
+ raise ArgumentError, "Il padding deve essere uno tra: #{PANEL_PADDING_CLASSES.keys.join(', ')}"
238
+ end
239
+ end
240
+
241
+ def validate_radius
242
+ unless PANEL_RADIUS_CLASSES.keys.include?(@radius)
243
+ raise ArgumentError, "Il raggio deve essere uno tra: #{PANEL_RADIUS_CLASSES.keys.join(', ')}"
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,11 @@
1
+ <div class="bui-progress-wrapper">
2
+ <div <%= progress_attributes.map { |k, v| "#{k}=\"#{v}\"" }.join(' ').html_safe %>>
3
+ <div <%= bar_attributes.map { |k, v| "#{k}=\"#{v}\"" }.join(' ').html_safe %>></div>
4
+ </div>
5
+
6
+ <% if show_label? %>
7
+ <div class="bui-progress-label mt-1 text-sm text-gray-600 text-center">
8
+ <%= value %>%
9
+ </div>
10
+ <% end %>
11
+ </div>
@@ -0,0 +1,160 @@
1
+ module BetterUi
2
+ module General
3
+ module Progress
4
+ class Component < ViewComponent::Base
5
+ # Classi base sempre presenti
6
+ PROGRESS_BASE_CLASSES = "relative w-full bg-gray-200 rounded-full overflow-hidden"
7
+
8
+ # Classi per la barra di progresso
9
+ PROGRESS_BAR_BASE_CLASSES = "h-full transition-all duration-300 ease-in-out"
10
+
11
+ # Dimensioni della progress bar con classi Tailwind dirette
12
+ PROGRESS_SIZES = {
13
+ small: "h-2",
14
+ medium: "h-4",
15
+ large: "h-6"
16
+ }
17
+
18
+ # Temi di progress bar con classi Tailwind dirette
19
+ PROGRESS_THEMES = {
20
+ default: "bg-gray-600",
21
+ white: "bg-white border border-gray-300",
22
+ red: "bg-red-600",
23
+ rose: "bg-rose-600",
24
+ orange: "bg-orange-600",
25
+ green: "bg-green-600",
26
+ blue: "bg-blue-600",
27
+ yellow: "bg-yellow-600",
28
+ violet: "bg-violet-600"
29
+ }
30
+
31
+ # Classi per il background container
32
+ PROGRESS_CONTAINER_THEMES = {
33
+ default: "bg-gray-200",
34
+ white: "bg-gray-100",
35
+ red: "bg-red-100",
36
+ rose: "bg-rose-100",
37
+ orange: "bg-orange-100",
38
+ green: "bg-green-100",
39
+ blue: "bg-blue-100",
40
+ yellow: "bg-yellow-100",
41
+ violet: "bg-violet-100"
42
+ }
43
+
44
+ # @param value [Integer] percentuale di completamento (0-100)
45
+ # @param theme [Symbol] :default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet
46
+ # @param size [Symbol] :small, :medium, :large
47
+ # @param label [Boolean] mostra etichetta con percentuale
48
+ # @param classes [String] classi CSS aggiuntive per il container
49
+ # @param html_options [Hash] opzioni HTML per il container
50
+ def initialize(
51
+ value: 0,
52
+ theme: :white,
53
+ size: :medium,
54
+ label: false,
55
+ classes: nil,
56
+ **html_options
57
+ )
58
+ @value = [ 0, [ value.to_i, 100 ].min ].max # Clamp tra 0 e 100
59
+ @theme = theme.to_sym
60
+ @size = size.to_sym
61
+ @label = label
62
+ @classes = classes
63
+ @html_options = html_options
64
+
65
+ validate_params
66
+ end
67
+
68
+ # Combina tutte le classi per il container
69
+ def combined_classes
70
+ [
71
+ PROGRESS_BASE_CLASSES,
72
+ get_size_class,
73
+ get_container_theme_class,
74
+ @classes,
75
+ @html_options[:class]
76
+ ].compact.join(" ")
77
+ end
78
+
79
+ # Combina tutte le classi per la barra di progresso
80
+ def bar_classes
81
+ [
82
+ PROGRESS_BAR_BASE_CLASSES,
83
+ get_theme_class
84
+ ].compact.join(" ")
85
+ end
86
+
87
+ # Restituisce gli attributi per il container della progress bar
88
+ def progress_attributes
89
+ attrs = {
90
+ class: combined_classes,
91
+ role: "progressbar",
92
+ "aria-valuenow": @value,
93
+ "aria-valuemin": 0,
94
+ "aria-valuemax": 100,
95
+ "aria-label": "Progresso: #{@value}%"
96
+ }
97
+
98
+ # Aggiungi altri attributi HTML se presenti
99
+ @html_options.except(:class).each do |key, value|
100
+ attrs[key] = value
101
+ end
102
+
103
+ attrs
104
+ end
105
+
106
+ # Restituisce gli attributi per la barra di progresso
107
+ def bar_attributes
108
+ {
109
+ class: bar_classes,
110
+ style: "width: #{@value}%"
111
+ }
112
+ end
113
+
114
+ # Restituisce il valore percentuale
115
+ attr_reader :value
116
+
117
+ # Verifica se mostrare l'etichetta
118
+ def show_label?
119
+ @label
120
+ end
121
+
122
+ private
123
+
124
+ def validate_params
125
+ validate_theme
126
+ validate_size
127
+ end
128
+
129
+ def validate_theme
130
+ valid_themes = PROGRESS_THEMES.keys
131
+ unless valid_themes.include?(@theme)
132
+ raise ArgumentError, "Il tema deve essere uno tra: #{valid_themes.join(', ')}"
133
+ end
134
+ end
135
+
136
+ def validate_size
137
+ valid_sizes = PROGRESS_SIZES.keys
138
+ unless valid_sizes.include?(@size)
139
+ raise ArgumentError, "La dimensione deve essere una tra: #{valid_sizes.join(', ')}"
140
+ end
141
+ end
142
+
143
+ # Genera le classi per la dimensione
144
+ def get_size_class
145
+ PROGRESS_SIZES[@size] || PROGRESS_SIZES[:medium]
146
+ end
147
+
148
+ # Genera le classi per il tema della barra
149
+ def get_theme_class
150
+ PROGRESS_THEMES[@theme] || PROGRESS_THEMES[:white]
151
+ end
152
+
153
+ # Genera le classi per il tema del container
154
+ def get_container_theme_class
155
+ PROGRESS_CONTAINER_THEMES[@theme] || PROGRESS_CONTAINER_THEMES[:white]
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,35 @@
1
+ <div <%= tag.attributes(container_attributes) %>>
2
+ <div class="flex-shrink-0">
3
+ <svg class="<%= svg_classes %> animate-spin" viewBox="0 0 24 24" fill="none">
4
+ <circle
5
+ cx="12"
6
+ cy="12"
7
+ r="10"
8
+ stroke="currentColor"
9
+ stroke-width="2"
10
+ stroke-linecap="round"
11
+ stroke-dasharray="32"
12
+ stroke-dashoffset="32">
13
+ <animate
14
+ attributeName="stroke-dasharray"
15
+ dur="2s"
16
+ values="0 32;16 16;0 32;0 32"
17
+ repeatCount="indefinite" />
18
+ <animate
19
+ attributeName="stroke-dashoffset"
20
+ dur="2s"
21
+ values="0;-16;-32;-32"
22
+ repeatCount="indefinite" />
23
+ <animateTransform
24
+ attributeName="transform"
25
+ type="rotate"
26
+ dur="2s"
27
+ values="0 12 12;360 12 12"
28
+ repeatCount="indefinite" />
29
+ </circle>
30
+ </svg>
31
+ </div>
32
+ <% if show_label? %>
33
+ <span class="text-sm font-medium"><%= label %></span>
34
+ <% end %>
35
+ </div>