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,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Field
6
+ class Component < ViewComponent::Base
7
+ attr_reader :label, :name, :required, :error, :help_text, :id
8
+
9
+ renders_one :input
10
+
11
+ BASE_CLASSES = "flex flex-col space-y-2"
12
+ LABEL_CLASSES = "text-sm font-medium text-gray-700"
13
+ REQUIRED_CLASSES = "text-red-500 ml-1"
14
+ ERROR_CLASSES = "text-sm text-red-600 mt-1"
15
+ HELP_TEXT_CLASSES = "text-sm text-gray-500 mt-1"
16
+
17
+ def initialize(label:, name:, required: false, error: nil, help_text: nil, id: nil)
18
+ @label = label
19
+ @name = name
20
+ @required = required
21
+ @error = error
22
+ @help_text = help_text
23
+ @id = id
24
+ super()
25
+ end
26
+
27
+ private
28
+
29
+ def field_classes
30
+ classes = [BASE_CLASSES]
31
+ classes << ERROR_CLASSES if @error.present?
32
+ classes.join(" ")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ <%# Template per il heading %>
2
+ <div class="<%= container_classes %>">
3
+ <%= tag.public_send(heading_tag, **heading_attributes) do %>
4
+ <% if show_icon? %>
5
+ <span class="<%= icon_classes %>">
6
+ <%= render BetterUi::General::IconComponent.new(name: @icon) %>
7
+ </span>
8
+ <% end %>
9
+
10
+ <span><%= content %></span>
11
+ <% end %>
12
+
13
+ <% if show_subtitle? %>
14
+ <div class="<%= subtitle_classes %>">
15
+ <%= @subtitle %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <% if show_divider? %>
20
+ <div class="<%= divider_classes %>"></div>
21
+ <% end %>
22
+ </div>
@@ -0,0 +1,257 @@
1
+ module BetterUi
2
+ module General
3
+ module Heading
4
+ class Component < ViewComponent::Base
5
+ attr_reader :level, :theme, :align, :size, :style, :icon, :subtitle, :with_divider
6
+
7
+ # Classi base sempre presenti
8
+ HEADING_BASE_CLASSES = "font-bold leading-tight"
9
+
10
+ # Temi con classi Tailwind dirette - LOGICA CORRETTA
11
+ HEADING_THEME_CLASSES = {
12
+ default: "text-white", # Testo bianco (per sfondi scuri)
13
+ white: "text-gray-900", # Testo nero (per sfondi chiari)
14
+ red: "text-red-500",
15
+ rose: "text-rose-500",
16
+ orange: "text-orange-500",
17
+ green: "text-green-500",
18
+ blue: "text-blue-500",
19
+ yellow: "text-yellow-600",
20
+ violet: "text-violet-500",
21
+ purple: "text-purple-500"
22
+ }
23
+
24
+ # Allineamenti con classi Tailwind dirette
25
+ HEADING_ALIGN_CLASSES = {
26
+ left: "text-left",
27
+ center: "text-center",
28
+ right: "text-right"
29
+ }
30
+
31
+ # Dimensioni base (verranno combinate con level)
32
+ HEADING_SIZE_CLASSES = {
33
+ small: {
34
+ 1 => "text-2xl sm:text-3xl",
35
+ 2 => "text-xl sm:text-2xl",
36
+ 3 => "text-lg sm:text-xl",
37
+ 4 => "text-base sm:text-lg",
38
+ 5 => "text-sm sm:text-base",
39
+ 6 => "text-xs sm:text-sm"
40
+ },
41
+ medium: {
42
+ 1 => "text-3xl sm:text-4xl",
43
+ 2 => "text-2xl sm:text-3xl",
44
+ 3 => "text-xl sm:text-2xl",
45
+ 4 => "text-lg sm:text-xl",
46
+ 5 => "text-base sm:text-lg",
47
+ 6 => "text-sm sm:text-base"
48
+ },
49
+ large: {
50
+ 1 => "text-4xl sm:text-5xl",
51
+ 2 => "text-3xl sm:text-4xl",
52
+ 3 => "text-2xl sm:text-3xl",
53
+ 4 => "text-xl sm:text-2xl",
54
+ 5 => "text-lg sm:text-xl",
55
+ 6 => "text-base sm:text-lg"
56
+ }
57
+ }
58
+
59
+ # Stili con classi Tailwind dirette
60
+ HEADING_STYLE_CLASSES = {
61
+ normal: "",
62
+ bold: "font-extrabold",
63
+ italic: "italic",
64
+ underline: "underline"
65
+ }
66
+
67
+ # Temi per subtitle - LOGICA CORRETTA
68
+ HEADING_SUBTITLE_THEME_CLASSES = {
69
+ default: "text-gray-300", # Testo grigio chiaro (per sfondi scuri)
70
+ white: "text-gray-600", # Testo grigio scuro (per sfondi chiari)
71
+ red: "text-red-400",
72
+ rose: "text-rose-400",
73
+ orange: "text-orange-400",
74
+ green: "text-green-400",
75
+ blue: "text-blue-400",
76
+ yellow: "text-yellow-500",
77
+ violet: "text-violet-400"
78
+ }
79
+
80
+ # Temi per divider - LOGICA CORRETTA
81
+ HEADING_DIVIDER_THEME_CLASSES = {
82
+ default: "border-gray-700", # Bordo grigio scuro (per sfondi scuri)
83
+ white: "border-gray-200", # Bordo grigio chiaro (per sfondi chiari)
84
+ red: "border-red-200",
85
+ rose: "border-rose-200",
86
+ orange: "border-orange-200",
87
+ green: "border-green-200",
88
+ blue: "border-blue-200",
89
+ yellow: "border-yellow-200",
90
+ violet: "border-violet-200"
91
+ }
92
+
93
+ # @param level [Integer] livello del heading (1-6)
94
+ # @param theme [Symbol] tema del colore (:default, :white, etc.)
95
+ # @param align [Symbol] allineamento (:left, :center, :right)
96
+ # @param size [Symbol] dimensione (:small, :medium, :large)
97
+ # @param style [Symbol] stile (:normal, :bold, :italic, :underline)
98
+ # @param icon [String] icona opzionale
99
+ # @param subtitle [String] sottotitolo opzionale
100
+ # @param with_divider [Boolean] mostra linea divisoria
101
+ # @param html_options [Hash] opzioni HTML per il container
102
+ def initialize(
103
+ level: 2,
104
+ theme: :white,
105
+ align: :left,
106
+ size: :medium,
107
+ style: :normal,
108
+ icon: nil,
109
+ subtitle: nil,
110
+ with_divider: false,
111
+ **html_options
112
+ )
113
+ @level = level.to_i.clamp(1, 6)
114
+ @theme = theme.to_sym
115
+ @align = align.to_sym
116
+ @size = size.to_sym
117
+ @style = style.to_sym
118
+ @icon = icon
119
+ @subtitle = subtitle
120
+ @with_divider = with_divider
121
+ @html_options = html_options
122
+
123
+ validate_params
124
+ end
125
+
126
+ # Combina tutte le classi per il heading
127
+ def heading_classes
128
+ [
129
+ HEADING_BASE_CLASSES,
130
+ get_theme_class,
131
+ get_align_class,
132
+ get_size_class,
133
+ get_style_class,
134
+ @html_options[:class]
135
+ ].compact.join(" ")
136
+ end
137
+
138
+ # Classi per il container principale
139
+ def container_classes
140
+ "mb-4"
141
+ end
142
+
143
+ # Classi per il subtitle
144
+ def subtitle_classes
145
+ return "" unless @subtitle.present?
146
+
147
+ [
148
+ "mt-1 text-sm",
149
+ get_subtitle_theme_class,
150
+ get_align_class
151
+ ].compact.join(" ")
152
+ end
153
+
154
+ # Classi per il divider
155
+ def divider_classes
156
+ return "" unless @with_divider
157
+
158
+ [
159
+ "mt-2 border-t",
160
+ get_divider_theme_class
161
+ ].compact.join(" ")
162
+ end
163
+
164
+ # Classi per l'icona
165
+ def icon_classes
166
+ return "" unless @icon.present?
167
+ "mr-2 inline-block"
168
+ end
169
+
170
+ # Restituisce gli attributi HTML per il heading
171
+ def heading_attributes
172
+ attrs = @html_options.except(:class)
173
+ attrs[:class] = heading_classes
174
+ attrs
175
+ end
176
+
177
+ # Tag del heading basato sul level
178
+ def heading_tag
179
+ "h#{@level}"
180
+ end
181
+
182
+ # Determina se mostrare l'icona
183
+ def show_icon?
184
+ @icon.present?
185
+ end
186
+
187
+ # Determina se mostrare il subtitle
188
+ def show_subtitle?
189
+ @subtitle.present?
190
+ end
191
+
192
+ # Determina se mostrare il divider
193
+ def show_divider?
194
+ @with_divider
195
+ end
196
+
197
+ private
198
+
199
+ def get_theme_class
200
+ HEADING_THEME_CLASSES[@theme] || HEADING_THEME_CLASSES[:white]
201
+ end
202
+
203
+ def get_align_class
204
+ HEADING_ALIGN_CLASSES[@align] || HEADING_ALIGN_CLASSES[:left]
205
+ end
206
+
207
+ def get_size_class
208
+ size_map = HEADING_SIZE_CLASSES[@size] || HEADING_SIZE_CLASSES[:medium]
209
+ size_map[@level] || size_map[2]
210
+ end
211
+
212
+ def get_style_class
213
+ HEADING_STYLE_CLASSES[@style] || HEADING_STYLE_CLASSES[:normal]
214
+ end
215
+
216
+ def get_subtitle_theme_class
217
+ HEADING_SUBTITLE_THEME_CLASSES[@theme] || HEADING_SUBTITLE_THEME_CLASSES[:white]
218
+ end
219
+
220
+ def get_divider_theme_class
221
+ HEADING_DIVIDER_THEME_CLASSES[@theme] || HEADING_DIVIDER_THEME_CLASSES[:white]
222
+ end
223
+
224
+ def validate_params
225
+ validate_theme
226
+ validate_align
227
+ validate_size
228
+ validate_style
229
+ end
230
+
231
+ def validate_theme
232
+ unless HEADING_THEME_CLASSES.keys.include?(@theme)
233
+ raise ArgumentError, "Il tema deve essere uno tra: #{HEADING_THEME_CLASSES.keys.join(', ')}"
234
+ end
235
+ end
236
+
237
+ def validate_align
238
+ unless HEADING_ALIGN_CLASSES.keys.include?(@align)
239
+ raise ArgumentError, "L'allineamento deve essere uno tra: #{HEADING_ALIGN_CLASSES.keys.join(', ')}"
240
+ end
241
+ end
242
+
243
+ def validate_size
244
+ unless HEADING_SIZE_CLASSES.keys.include?(@size)
245
+ raise ArgumentError, "La dimensione deve essere una tra: #{HEADING_SIZE_CLASSES.keys.join(', ')}"
246
+ end
247
+ end
248
+
249
+ def validate_style
250
+ unless HEADING_STYLE_CLASSES.keys.include?(@style)
251
+ raise ArgumentError, "Lo stile deve essere uno tra: #{HEADING_STYLE_CLASSES.keys.join(', ')}"
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,7 @@
1
+ <% if href.present? %>
2
+ <%= tag.a(**icon_attributes) do %>
3
+ <i class="<%= fa_class_name %> <%= icon_classes %>"></i>
4
+ <% end %>
5
+ <% else %>
6
+ <i class="<%= fa_class_name %> <%= icon_classes %>"></i>
7
+ <% end %>
@@ -0,0 +1,239 @@
1
+ module BetterUi
2
+ module General
3
+ module Icon
4
+ class Component < ViewComponent::Base
5
+ # Classi base per l'icona con nomenclatura BEM
6
+ ICON_BASE_CLASSES = "bui-icon inline-flex items-center justify-center"
7
+
8
+ # Dimensioni dell'icona (standardizzate: small, medium, large)
9
+ ICON_SIZE_CLASSES = {
10
+ small: "bui-icon--small w-4 h-4 text-sm",
11
+ medium: "bui-icon--medium w-5 h-5 text-base",
12
+ large: "bui-icon--large w-6 h-6 text-lg"
13
+ }.freeze
14
+
15
+ # Temi dell'icona con colori coerenti
16
+ ICON_THEME_CLASSES = {
17
+ default: "bui-icon--default text-gray-600",
18
+ white: "bui-icon--white text-white",
19
+ red: "bui-icon--red text-red-600",
20
+ rose: "bui-icon--rose text-rose-600",
21
+ orange: "bui-icon--orange text-orange-600",
22
+ green: "bui-icon--green text-green-600",
23
+ blue: "bui-icon--blue text-blue-600",
24
+ yellow: "bui-icon--yellow text-yellow-600",
25
+ violet: "bui-icon--violet text-violet-600",
26
+ purple: "bui-icon--purple text-purple-600"
27
+ }.freeze
28
+
29
+ # Animazioni disponibili
30
+ ICON_ANIMATION_CLASSES = {
31
+ spin: "bui-icon--spin animate-spin",
32
+ pulse: "bui-icon--pulse animate-pulse"
33
+ }.freeze
34
+
35
+ # Trasformazioni disponibili
36
+ ICON_ROTATION_CLASSES = {
37
+ 90 => "bui-icon--rotate-90 transform rotate-90",
38
+ 180 => "bui-icon--rotate-180 transform rotate-180",
39
+ 270 => "bui-icon--rotate-270 transform rotate-270"
40
+ }.freeze
41
+
42
+ ICON_FLIP_CLASSES = {
43
+ horizontal: "bui-icon--flip-h transform scale-x-[-1]",
44
+ vertical: "bui-icon--flip-v transform scale-y-[-1]",
45
+ both: "bui-icon--flip-both transform scale-[-1]"
46
+ }.freeze
47
+
48
+ # Bordo e larghezza fissa
49
+ ICON_BORDER_CLASSES = "bui-icon--border border border-current rounded-full p-1"
50
+ ICON_FIXED_WIDTH_CLASSES = "bui-icon--fixed-width w-5"
51
+
52
+ attr_reader :name, :style, :size, :theme, :spin, :pulse, :border, :fixed_width,
53
+ :rotation, :flip, :classes, :id, :href, :method, :target, :html_options
54
+
55
+ # @param name [String] Nome dell'icona (richiesto)
56
+ # @param style [Symbol] Stile dell'icona (:solid, :regular, :brands)
57
+ # @param size [Symbol] Dimensione (:small, :medium, :large)
58
+ # @param theme [Symbol] Tema colore (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
59
+ # @param spin [Boolean] Rotazione continua
60
+ # @param pulse [Boolean] Animazione pulsazione
61
+ # @param border [Boolean] Mostra bordo attorno all'icona
62
+ # @param fixed_width [Boolean] Larghezza fissa per allineamento
63
+ # @param rotation [Integer] Rotazione in gradi (0, 90, 180, 270)
64
+ # @param flip [Symbol] Tipo di flip (:horizontal, :vertical, :both)
65
+ # @param classes [String] Classi CSS aggiuntive
66
+ # @param id [String] ID HTML
67
+ # @param href [String] URL per l'icona
68
+ # @param html_options [Hash] Attributi HTML aggiuntivi
69
+ def initialize(
70
+ name:,
71
+ style: :solid,
72
+ size: :medium,
73
+ theme: :default,
74
+ spin: false,
75
+ pulse: false,
76
+ border: false,
77
+ fixed_width: false,
78
+ rotation: nil,
79
+ flip: nil,
80
+ classes: nil,
81
+ id: nil,
82
+ href: nil,
83
+ method: nil,
84
+ target: nil,
85
+ **html_options
86
+ )
87
+ @name = name
88
+ @style = style.to_sym
89
+ @size = size.to_sym
90
+ @theme = theme.to_sym
91
+ @spin = spin
92
+ @pulse = pulse
93
+ @border = border
94
+ @fixed_width = fixed_width
95
+ @rotation = rotation&.to_i
96
+ @flip = flip&.to_sym
97
+ @classes = classes
98
+ @id = id
99
+ @href = href
100
+ @method = method
101
+ @target = target
102
+ @html_options = html_options
103
+
104
+ validate_params!
105
+ end
106
+
107
+ # Genera le classi CSS complete per l'icona
108
+ def icon_classes
109
+ classes = [
110
+ ICON_BASE_CLASSES,
111
+ size_classes,
112
+ theme_classes,
113
+ animation_classes,
114
+ transformation_classes,
115
+ border_classes,
116
+ fixed_width_classes,
117
+ @classes
118
+ ].compact.join(" ")
119
+ end
120
+
121
+ # Attributi HTML per l'elemento icona
122
+ def icon_attributes
123
+ attrs = {
124
+ class: icon_classes,
125
+ id: @id,
126
+ href: @href,
127
+ target: @target,
128
+ **@html_options
129
+ }.compact
130
+
131
+ # Handle method for Turbo
132
+ if @method.present?
133
+ attrs[:data] ||= {}
134
+ attrs[:data][:turbo_method] = @method
135
+ end
136
+
137
+ attrs
138
+ end
139
+
140
+ # Nome completo della classe FontAwesome basato su stile
141
+ def fa_class_name
142
+ prefix = case @style
143
+ when :solid then "fas"
144
+ when :regular then "far"
145
+ when :brands then "fab"
146
+ else "fas"
147
+ end
148
+
149
+ "#{prefix} fa-#{@name}"
150
+ end
151
+
152
+ # Verifica se il componente deve essere renderizzato
153
+ def render?
154
+ @name.present?
155
+ end
156
+
157
+ private
158
+
159
+ def validate_params!
160
+ validate_name!
161
+ validate_style!
162
+ validate_size!
163
+ validate_theme!
164
+ validate_rotation!
165
+ validate_flip!
166
+ end
167
+
168
+ def validate_name!
169
+ raise ArgumentError, "Il nome dell'icona è richiesto" if @name.blank?
170
+ end
171
+
172
+ def validate_style!
173
+ valid_styles = [ :solid, :regular, :brands ]
174
+ unless valid_styles.include?(@style)
175
+ raise ArgumentError, "Lo stile deve essere uno tra: #{valid_styles.join(', ')}"
176
+ end
177
+ end
178
+
179
+ def validate_size!
180
+ unless ICON_SIZE_CLASSES.key?(@size)
181
+ valid_sizes = ICON_SIZE_CLASSES.keys
182
+ raise ArgumentError, "La dimensione deve essere una tra: #{valid_sizes.join(', ')}"
183
+ end
184
+ end
185
+
186
+ def validate_theme!
187
+ unless ICON_THEME_CLASSES.key?(@theme)
188
+ valid_themes = ICON_THEME_CLASSES.keys
189
+ raise ArgumentError, "Il tema deve essere uno tra: #{valid_themes.join(', ')}"
190
+ end
191
+ end
192
+
193
+ def validate_rotation!
194
+ if @rotation && !ICON_ROTATION_CLASSES.key?(@rotation)
195
+ valid_rotations = ICON_ROTATION_CLASSES.keys
196
+ raise ArgumentError, "La rotazione deve essere una tra: #{valid_rotations.join(', ')}"
197
+ end
198
+ end
199
+
200
+ def validate_flip!
201
+ if @flip && !ICON_FLIP_CLASSES.key?(@flip)
202
+ valid_flips = ICON_FLIP_CLASSES.keys
203
+ raise ArgumentError, "Il flip deve essere uno tra: #{valid_flips.join(', ')}"
204
+ end
205
+ end
206
+
207
+ def size_classes
208
+ ICON_SIZE_CLASSES[@size]
209
+ end
210
+
211
+ def theme_classes
212
+ ICON_THEME_CLASSES[@theme]
213
+ end
214
+
215
+ def animation_classes
216
+ animations = []
217
+ animations << ICON_ANIMATION_CLASSES[:spin] if @spin
218
+ animations << ICON_ANIMATION_CLASSES[:pulse] if @pulse
219
+ animations.join(" ")
220
+ end
221
+
222
+ def transformation_classes
223
+ transformations = []
224
+ transformations << ICON_ROTATION_CLASSES[@rotation] if @rotation
225
+ transformations << ICON_FLIP_CLASSES[@flip] if @flip
226
+ transformations.join(" ")
227
+ end
228
+
229
+ def border_classes
230
+ @border ? ICON_BORDER_CLASSES : nil
231
+ end
232
+
233
+ def fixed_width_classes
234
+ @fixed_width ? ICON_FIXED_WIDTH_CLASSES : nil
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,5 @@
1
+ <% if @label.present? %>
2
+ <%= render_checkbox_with_label %>
3
+ <% else %>
4
+ <%= input_tag %>
5
+ <% end %>