better_ui 0.1.0 → 0.1.1

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 (118) 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/field/component.html.erb +27 -0
  26. data/app/components/better_ui/general/field/component.rb +37 -0
  27. data/app/components/better_ui/general/heading/component.html.erb +22 -0
  28. data/app/components/better_ui/general/heading/component.rb +257 -0
  29. data/app/components/better_ui/general/icon/component.html.erb +7 -0
  30. data/app/components/better_ui/general/icon/component.rb +239 -0
  31. data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
  32. data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
  33. data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
  34. data/app/components/better_ui/general/input/datetime/component.rb +223 -0
  35. data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
  36. data/app/components/better_ui/general/input/radio/component.rb +230 -0
  37. data/app/components/better_ui/general/input/select/component.html.erb +16 -0
  38. data/app/components/better_ui/general/input/select/component.rb +184 -0
  39. data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
  40. data/app/components/better_ui/general/input/select/select_component.rb +37 -0
  41. data/app/components/better_ui/general/input/text/component.html.erb +5 -0
  42. data/app/components/better_ui/general/input/text/component.rb +171 -0
  43. data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
  44. data/app/components/better_ui/general/input/textarea/component.rb +166 -0
  45. data/app/components/better_ui/general/link/component.html.erb +18 -0
  46. data/app/components/better_ui/general/link/component.rb +258 -0
  47. data/app/components/better_ui/general/panel/component.html.erb +28 -0
  48. data/app/components/better_ui/general/panel/component.rb +249 -0
  49. data/app/components/better_ui/general/progress/component.html.erb +11 -0
  50. data/app/components/better_ui/general/progress/component.rb +160 -0
  51. data/app/components/better_ui/general/spinner/component.html.erb +35 -0
  52. data/app/components/better_ui/general/spinner/component.rb +93 -0
  53. data/app/components/better_ui/general/table/component.html.erb +5 -0
  54. data/app/components/better_ui/general/table/component.rb +217 -0
  55. data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
  56. data/app/components/better_ui/general/table/tbody_component.rb +30 -0
  57. data/app/components/better_ui/general/table/td_component.html.erb +3 -0
  58. data/app/components/better_ui/general/table/td_component.rb +44 -0
  59. data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
  60. data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
  61. data/app/components/better_ui/general/table/th_component.html.erb +6 -0
  62. data/app/components/better_ui/general/table/th_component.rb +51 -0
  63. data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
  64. data/app/components/better_ui/general/table/thead_component.rb +28 -0
  65. data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
  66. data/app/components/better_ui/general/table/tr_component.rb +30 -0
  67. data/app/components/better_ui/general/tag/component.html.erb +3 -0
  68. data/app/components/better_ui/general/tag/component.rb +104 -0
  69. data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
  70. data/app/components/better_ui/general/tooltip/component.rb +239 -0
  71. data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
  72. data/app/helpers/better_ui/application/components/card.rb +11 -0
  73. data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
  74. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
  75. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
  76. data/app/helpers/better_ui/application_helper.rb +42 -179
  77. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
  78. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
  79. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
  80. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
  81. data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
  82. data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
  83. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
  84. data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
  85. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
  86. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
  87. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
  88. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
  89. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
  90. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
  91. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
  92. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
  93. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
  94. data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
  95. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
  96. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
  97. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
  98. data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
  99. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
  100. data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
  101. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
  102. data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
  103. data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
  104. data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
  105. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
  106. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
  107. data/app/views/layouts/better_ui/application.html.erb +6 -124
  108. data/config/initializers/lookbook.rb +23 -0
  109. data/config/routes.rb +0 -8
  110. data/lib/better_ui/engine.rb +5 -19
  111. data/lib/better_ui/railtie.rb +20 -0
  112. data/lib/better_ui/version.rb +1 -1
  113. data/lib/better_ui.rb +4 -20
  114. metadata +131 -28
  115. data/app/controllers/better_ui/docs_controller.rb +0 -41
  116. data/app/views/better_ui/docs/component.html.erb +0 -365
  117. data/app/views/better_ui/docs/index.html.erb +0 -100
  118. data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -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>
@@ -0,0 +1,93 @@
1
+ module BetterUi
2
+ module General
3
+ module Spinner
4
+ class Component < ViewComponent::Base
5
+ THEMES = %i[default white red rose orange green blue yellow violet].freeze
6
+ SIZES = %i[small medium large].freeze
7
+ STYLES = %i[default outline].freeze
8
+
9
+ # Classi base sempre presenti
10
+ SPINNER_BASE_CLASSES = "inline-flex items-center gap-2"
11
+
12
+ # Dimensioni SVG con classi Tailwind dirette
13
+ SPINNER_SIZES = {
14
+ small: "w-4 h-4", # 16px
15
+ medium: "w-6 h-6", # 24px
16
+ large: "w-8 h-8" # 32px
17
+ }
18
+
19
+ # Temi colore con classi Tailwind dirette
20
+ SPINNER_THEMES = {
21
+ default: "text-gray-900",
22
+ white: "text-white",
23
+ red: "text-red-500",
24
+ rose: "text-rose-500",
25
+ orange: "text-orange-500",
26
+ green: "text-green-500",
27
+ blue: "text-blue-500",
28
+ yellow: "text-yellow-500",
29
+ violet: "text-violet-500"
30
+ }
31
+
32
+ # Stili con classi Tailwind dirette
33
+ SPINNER_STYLES = {
34
+ default: "",
35
+ outline: "opacity-75"
36
+ }
37
+
38
+ def initialize(theme: :default, size: :medium, style: :default, label: nil, **html_options)
39
+ @theme = theme.to_sym
40
+ @size = size.to_sym
41
+ @style = style.to_sym
42
+ @label = label
43
+ @html_options = html_options
44
+
45
+ validate_options!
46
+ end
47
+
48
+ private
49
+
50
+ attr_reader :theme, :size, :style, :label, :html_options
51
+
52
+ def validate_options!
53
+ raise ArgumentError, "Theme deve essere uno di: #{THEMES.join(', ')}" unless THEMES.include?(theme)
54
+ raise ArgumentError, "Size deve essere uno di: #{SIZES.join(', ')}" unless SIZES.include?(size)
55
+ raise ArgumentError, "Style deve essere uno di: #{STYLES.join(', ')}" unless STYLES.include?(style)
56
+ end
57
+
58
+ def combined_classes
59
+ [
60
+ SPINNER_BASE_CLASSES,
61
+ get_spinner_theme_classes,
62
+ get_spinner_style_classes,
63
+ html_options[:class]
64
+ ].compact.join(" ")
65
+ end
66
+
67
+ def get_spinner_theme_classes
68
+ SPINNER_THEMES[theme] || SPINNER_THEMES[:default]
69
+ end
70
+
71
+ def get_spinner_style_classes
72
+ SPINNER_STYLES[style] || SPINNER_STYLES[:default]
73
+ end
74
+
75
+ def get_spinner_size_classes
76
+ SPINNER_SIZES[size] || SPINNER_SIZES[:medium]
77
+ end
78
+
79
+ def container_attributes
80
+ html_options.except(:class).merge(class: combined_classes)
81
+ end
82
+
83
+ def svg_classes
84
+ get_spinner_size_classes
85
+ end
86
+
87
+ def show_label?
88
+ label.present?
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,5 @@
1
+ <div <%= tag.attributes(container_attributes) %>>
2
+ <table <%= tag.attributes(table_attributes) %>>
3
+ <%= content %>
4
+ </table>
5
+ </div>