better_ui 0.1.0 → 0.5.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +199 -75
  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 +202 -0
  11. data/app/components/better_ui/application/card_component.html.erb +24 -0
  12. data/app/components/better_ui/application/card_component.rb +53 -0
  13. data/app/components/better_ui/application/card_container_component.html.erb +8 -0
  14. data/app/components/better_ui/application/card_container_component.rb +14 -0
  15. data/app/components/better_ui/application/header_component.html.erb +88 -0
  16. data/app/components/better_ui/application/header_component.rb +188 -0
  17. data/app/components/better_ui/application/navbar_component.html.erb +294 -0
  18. data/app/components/better_ui/application/navbar_component.rb +249 -0
  19. data/app/components/better_ui/application/sidebar_component.html.erb +207 -0
  20. data/app/components/better_ui/application/sidebar_component.rb +318 -0
  21. data/app/components/better_ui/application/toast_component.html.erb +35 -0
  22. data/app/components/better_ui/application/toast_component.rb +223 -0
  23. data/app/components/better_ui/general/avatar_component.html.erb +19 -0
  24. data/app/components/better_ui/general/avatar_component.rb +171 -0
  25. data/app/components/better_ui/general/badge_component.html.erb +19 -0
  26. data/app/components/better_ui/general/badge_component.rb +123 -0
  27. data/app/components/better_ui/general/breadcrumb_component.html.erb +15 -0
  28. data/app/components/better_ui/general/breadcrumb_component.rb +130 -0
  29. data/app/components/better_ui/general/button_component.html.erb +34 -0
  30. data/app/components/better_ui/general/button_component.rb +162 -0
  31. data/app/components/better_ui/general/heading_component.html.erb +25 -0
  32. data/app/components/better_ui/general/heading_component.rb +148 -0
  33. data/app/components/better_ui/general/icon_component.html.erb +2 -0
  34. data/app/components/better_ui/general/icon_component.rb +100 -0
  35. data/app/components/better_ui/general/link_component.html.erb +17 -0
  36. data/app/components/better_ui/general/link_component.rb +132 -0
  37. data/app/components/better_ui/general/panel_component.html.erb +27 -0
  38. data/app/components/better_ui/general/panel_component.rb +103 -0
  39. data/app/components/better_ui/general/spinner_component.html.erb +15 -0
  40. data/app/components/better_ui/general/spinner_component.rb +79 -0
  41. data/app/components/better_ui/general/table_component.html.erb +73 -0
  42. data/app/components/better_ui/general/table_component.rb +167 -0
  43. data/app/components/better_ui/theme_helper.rb +171 -0
  44. data/app/controllers/better_ui/application_controller.rb +1 -0
  45. data/app/controllers/better_ui/docs_controller.rb +18 -25
  46. data/app/views/components/better_ui/general/table/_custom_body_row.html.erb +17 -0
  47. data/app/views/components/better_ui/general/table/_custom_footer_rows.html.erb +17 -0
  48. data/app/views/components/better_ui/general/table/_custom_header_rows.html.erb +12 -0
  49. data/app/views/layouts/component_preview.html.erb +32 -0
  50. data/config/initializers/lookbook.rb +23 -0
  51. data/config/routes.rb +6 -1
  52. data/lib/better_ui/engine.rb +18 -1
  53. data/lib/better_ui/version.rb +1 -1
  54. data/lib/better_ui.rb +4 -0
  55. data/lib/generators/better_ui/stylesheet_generator.rb +96 -0
  56. data/lib/generators/better_ui/templates/README +56 -0
  57. data/lib/generators/better_ui/templates/components/_avatar.scss +151 -0
  58. data/lib/generators/better_ui/templates/components/_badge.scss +142 -0
  59. data/lib/generators/better_ui/templates/components/_breadcrumb.scss +107 -0
  60. data/lib/generators/better_ui/templates/components/_button.scss +106 -0
  61. data/lib/generators/better_ui/templates/components/_card.scss +69 -0
  62. data/lib/generators/better_ui/templates/components/_heading.scss +180 -0
  63. data/lib/generators/better_ui/templates/components/_icon.scss +90 -0
  64. data/lib/generators/better_ui/templates/components/_link.scss +130 -0
  65. data/lib/generators/better_ui/templates/components/_panel.scss +144 -0
  66. data/lib/generators/better_ui/templates/components/_spinner.scss +132 -0
  67. data/lib/generators/better_ui/templates/components/_table.scss +105 -0
  68. data/lib/generators/better_ui/templates/components/_variables.scss +33 -0
  69. data/lib/generators/better_ui/templates/components_stylesheet.scss +66 -0
  70. metadata +135 -10
  71. data/app/helpers/better_ui/application_helper.rb +0 -183
  72. data/app/views/better_ui/docs/component.html.erb +0 -365
  73. data/app/views/better_ui/docs/index.html.erb +0 -100
  74. data/app/views/better_ui/docs/show.html.erb +0 -60
  75. data/app/views/layouts/better_ui/application.html.erb +0 -135
@@ -12,360 +12,34 @@
12
12
  *
13
13
  *= require_tree .
14
14
  *= require_self
15
+ *= require font-awesome
15
16
  */
16
17
 
17
- /*
18
- * Better UI - Componenti UI per Rails
19
- *
20
- *= require_self
18
+ /*
19
+ * BetterUI - Utilizziamo Tailwind CSS per tutti gli stili
21
20
  */
22
21
 
23
- :root {
24
- /* Colori principali */
25
- --better-ui-primary: #007bff;
26
- --better-ui-secondary: #6c757d;
27
- --better-ui-success: #28a745;
28
- --better-ui-danger: #dc3545;
29
- --better-ui-warning: #ffc107;
30
- --better-ui-info: #17a2b8;
31
-
32
- /* Colori di sfondo */
33
- --better-ui-bg-light: #ffffff;
34
- --better-ui-bg-dark: #343a40;
35
-
36
- /* Colori di testo */
37
- --better-ui-text-dark: #343a40;
38
- --better-ui-text-light: #f8f9fa;
39
-
40
- /* Bordi */
41
- --better-ui-border-color: #dee2e6;
42
- --better-ui-border-radius: 0.25rem;
43
-
44
- /* Spaziature */
45
- --better-ui-spacer: 1rem;
46
- --better-ui-spacer-sm: 0.5rem;
47
- --better-ui-spacer-lg: 1.5rem;
48
-
49
- /* Transizioni */
50
- --better-ui-transition: all 0.2s ease-in-out;
51
- }
52
-
53
- /*
54
- * Button
55
- */
56
- .better-ui-button {
57
- display: inline-block;
58
- font-weight: 400;
59
- text-align: center;
60
- white-space: nowrap;
61
- vertical-align: middle;
62
- user-select: none;
63
- border: 1px solid transparent;
64
- padding: var(--better-ui-spacer-sm) var(--better-ui-spacer);
65
- font-size: 1rem;
22
+ /* Stili per il syntax highlighting di CodeRay */
23
+ .syntax-ruby {
24
+ color: #333;
25
+ background-color: #f8f8f8;
26
+ font-family: Monaco, Consolas, 'Courier New', monospace;
66
27
  line-height: 1.5;
67
- border-radius: var(--better-ui-border-radius);
68
- transition: var(--better-ui-transition);
69
- cursor: pointer;
70
- }
71
-
72
- .better-ui-button:focus {
73
- outline: 0;
74
- box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
75
- }
76
-
77
- .better-ui-button-primary {
78
- color: var(--better-ui-text-light);
79
- background-color: var(--better-ui-primary);
80
- border-color: var(--better-ui-primary);
81
- }
82
-
83
- .better-ui-button-primary:hover {
84
- background-color: #0069d9;
85
- border-color: #0062cc;
86
- }
87
-
88
- .better-ui-button-secondary {
89
- color: var(--better-ui-text-light);
90
- background-color: var(--better-ui-secondary);
91
- border-color: var(--better-ui-secondary);
92
- }
93
-
94
- .better-ui-button-secondary:hover {
95
- background-color: #5a6268;
96
- border-color: #545b62;
97
- }
98
-
99
- .better-ui-button-success {
100
- color: var(--better-ui-text-light);
101
- background-color: var(--better-ui-success);
102
- border-color: var(--better-ui-success);
103
- }
104
-
105
- .better-ui-button-success:hover {
106
- background-color: #218838;
107
- border-color: #1e7e34;
108
- }
109
-
110
- .better-ui-button-danger {
111
- color: var(--better-ui-text-light);
112
- background-color: var(--better-ui-danger);
113
- border-color: var(--better-ui-danger);
114
- }
115
-
116
- .better-ui-button-danger:hover {
117
- background-color: #c82333;
118
- border-color: #bd2130;
119
- }
120
-
121
- .better-ui-button:disabled {
122
- opacity: 0.65;
123
- cursor: not-allowed;
124
- }
125
-
126
- /*
127
- * Alert
128
- */
129
- .better-ui-alert {
130
- position: relative;
131
- padding: var(--better-ui-spacer);
132
- margin-bottom: var(--better-ui-spacer);
133
- border: 1px solid transparent;
134
- border-radius: var(--better-ui-border-radius);
135
- transition: var(--better-ui-transition);
136
- }
137
-
138
- .better-ui-alert-message {
139
- margin-right: 2rem;
140
- }
141
-
142
- .better-ui-alert-close {
143
- position: absolute;
144
- top: var(--better-ui-spacer-sm);
145
- right: var(--better-ui-spacer-sm);
146
- background: transparent;
147
- border: none;
148
- font-size: 1.25rem;
149
- font-weight: 700;
150
- line-height: 1;
151
- cursor: pointer;
152
- opacity: 0.5;
153
- transition: var(--better-ui-transition);
154
- }
155
-
156
- .better-ui-alert-close:hover {
157
- opacity: 1;
158
- }
159
-
160
- .better-ui-alert-info {
161
- color: #0c5460;
162
- background-color: #d1ecf1;
163
- border-color: #bee5eb;
164
- }
165
-
166
- .better-ui-alert-success {
167
- color: #155724;
168
- background-color: #d4edda;
169
- border-color: #c3e6cb;
170
- }
171
-
172
- .better-ui-alert-warning {
173
- color: #856404;
174
- background-color: #fff3cd;
175
- border-color: #ffeeba;
176
- }
177
-
178
- .better-ui-alert-danger {
179
- color: #721c24;
180
- background-color: #f8d7da;
181
- border-color: #f5c6cb;
182
- }
183
-
184
- .better-ui-alert-closing {
185
- opacity: 0;
186
- transform: translateY(-10px);
187
- }
188
-
189
- /*
190
- * Card
191
- */
192
- .better-ui-card {
193
- position: relative;
194
- display: flex;
195
- flex-direction: column;
196
- min-width: 0;
197
- word-wrap: break-word;
198
- background-color: var(--better-ui-bg-light);
199
- background-clip: border-box;
200
- border: 1px solid var(--better-ui-border-color);
201
- border-radius: var(--better-ui-border-radius);
202
- margin-bottom: var(--better-ui-spacer);
203
- }
204
-
205
- .better-ui-card-header {
206
- padding: 0.75rem 1.25rem;
207
- margin-bottom: 0;
208
- background-color: rgba(0, 0, 0, 0.03);
209
- border-bottom: 1px solid var(--better-ui-border-color);
210
- }
211
-
212
- .better-ui-card-title {
213
- margin: 0;
214
- font-size: 1.25rem;
215
- font-weight: 500;
216
- }
217
-
218
- .better-ui-card-body {
219
- flex: 1 1 auto;
220
- padding: 1.25rem;
221
- }
222
-
223
- .better-ui-card-footer {
224
- padding: 0.75rem 1.25rem;
225
- background-color: rgba(0, 0, 0, 0.03);
226
- border-top: 1px solid var(--better-ui-border-color);
227
- }
228
-
229
- /*
230
- * Tabs
231
- */
232
- .better-ui-tabs {
233
- margin-bottom: var(--better-ui-spacer);
234
- }
235
-
236
- .better-ui-tab-list {
237
- display: flex;
238
- flex-wrap: wrap;
239
- padding-left: 0;
240
- margin-bottom: 0;
241
- list-style: none;
242
- border-bottom: 1px solid var(--better-ui-border-color);
243
- }
244
-
245
- .better-ui-tab-item {
246
- display: block;
247
- padding: 0.5rem 1rem;
248
- border: 1px solid transparent;
249
- border-top-left-radius: var(--better-ui-border-radius);
250
- border-top-right-radius: var(--better-ui-border-radius);
251
- cursor: pointer;
252
- background: none;
253
- margin-bottom: -1px;
254
- }
255
-
256
- .better-ui-tab-item:hover {
257
- border-color: #e9ecef #e9ecef var(--better-ui-border-color);
258
- }
259
-
260
- .better-ui-tab-active {
261
- color: var(--better-ui-primary);
262
- background-color: var(--better-ui-bg-light);
263
- border-color: var(--better-ui-border-color) var(--better-ui-border-color) var(--better-ui-bg-light);
264
- }
265
-
266
- .better-ui-tab-content {
267
- padding: var(--better-ui-spacer);
268
- border: 1px solid var(--better-ui-border-color);
269
- border-top: 0;
270
- border-bottom-right-radius: var(--better-ui-border-radius);
271
- border-bottom-left-radius: var(--better-ui-border-radius);
272
- }
273
-
274
- /*
275
- * Modal
276
- */
277
- .better-ui-modal {
278
- position: fixed;
279
- top: 0;
280
- left: 0;
281
- width: 100%;
282
- height: 100%;
283
- z-index: 1050;
284
- display: none;
285
- overflow: hidden;
286
- align-items: center;
287
- justify-content: center;
288
- }
289
-
290
- .better-ui-modal-background {
291
- position: fixed;
292
- top: 0;
293
- left: 0;
294
- width: 100%;
295
- height: 100%;
296
- background-color: rgba(0, 0, 0, 0.5);
297
- opacity: 0;
298
- transition: opacity 0.3s ease;
299
- }
300
-
301
- .better-ui-modal-background-visible {
302
- opacity: 1;
303
- }
304
-
305
- .better-ui-modal-dialog {
306
- position: relative;
307
- width: 100%;
308
- max-width: 500px;
309
- margin: 1.75rem auto;
310
- background-color: var(--better-ui-bg-light);
311
- border-radius: var(--better-ui-border-radius);
312
- box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
313
- transform: translateY(-20px);
314
- opacity: 0;
315
- transition: transform 0.3s ease, opacity 0.3s ease;
316
- }
317
-
318
- .better-ui-modal-dialog-visible {
319
- transform: translateY(0);
320
- opacity: 1;
321
- }
322
-
323
- .better-ui-modal-header {
324
- display: flex;
325
- align-items: flex-start;
326
- justify-content: space-between;
327
- padding: 1rem;
328
- border-bottom: 1px solid var(--better-ui-border-color);
329
- }
330
-
331
- .better-ui-modal-title {
332
- margin: 0;
333
- font-size: 1.25rem;
334
- font-weight: 500;
335
- }
336
-
337
- .better-ui-modal-close {
338
- background: transparent;
339
- border: 0;
340
- font-size: 1.5rem;
341
- font-weight: 700;
342
- line-height: 1;
343
- color: var(--better-ui-text-dark);
344
- opacity: 0.5;
345
- cursor: pointer;
346
- }
347
-
348
- .better-ui-modal-close:hover {
349
- opacity: 1;
350
- }
351
-
352
- .better-ui-modal-body {
353
- position: relative;
354
- padding: 1rem;
355
- }
356
-
357
- .better-ui-modal-footer {
358
- display: flex;
359
- align-items: center;
360
- justify-content: flex-end;
361
- padding: 1rem;
362
- border-top: 1px solid var(--better-ui-border-color);
363
- }
364
-
365
- .better-ui-modal-footer > .better-ui-button {
366
- margin-left: 0.5rem;
367
- }
368
-
369
- body.better-ui-modal-open {
370
- overflow: hidden;
371
28
  }
29
+ .syntax-ruby .keyword { color: #0066CC; font-weight: bold; }
30
+ .syntax-ruby .string, .syntax-ruby .string-content { color: #008800; }
31
+ .syntax-ruby .comment { color: #888888; font-style: italic; }
32
+ .syntax-ruby .constant { color: #CC0000; }
33
+ .syntax-ruby .class { color: #0086B3; font-weight: bold; }
34
+ .syntax-ruby .module { color: #0086B3; font-weight: bold; }
35
+ .syntax-ruby .symbol { color: #AA6600; }
36
+ .syntax-ruby .function { color: #990000; }
37
+ .syntax-ruby .regexp { color: #AA6600; }
38
+ .syntax-ruby .integer, .syntax-ruby .float { color: #0000DD; }
39
+ .syntax-ruby .global-variable, .syntax-ruby .instance-variable { color: #336699; }
40
+ .syntax-ruby .predefined { color: #3377BB; font-weight: bold; }
41
+ .syntax-ruby .error { color: #FF0000; background-color: #FFAAAA; }
42
+ .syntax-ruby .delimiter { color: #555555; }
43
+ .syntax-ruby .method { color: #990000; font-weight: bold; }
44
+ .syntax-ruby .attribute-name { color: #994500; }
45
+ .syntax-ruby .value { color: #0066CC; }
@@ -0,0 +1,27 @@
1
+ <div role="alert" class="<%= container_classes %>" <%= @data&.map { |k, v| "data-#{k}=\"#{v}\"" }&.join(' ')&.html_safe %>>
2
+ <% if effective_icon.present? %>
3
+ <div class="<%= icon_classes %>">
4
+ <%= render BetterUi::General::IconComponent.new(name: effective_icon) %>
5
+ </div>
6
+ <% end %>
7
+
8
+ <div class="<%= content_classes %>">
9
+ <% if @title.present? %>
10
+ <div class="<%= title_classes %>"><%= @title %></div>
11
+ <% end %>
12
+
13
+ <% if @message.present? %>
14
+ <div class="<%= message_classes %>"><%= @message %></div>
15
+ <% end %>
16
+
17
+ <% if content.present? %>
18
+ <div class="<%= message_classes %>"><%= content %></div>
19
+ <% end %>
20
+ </div>
21
+
22
+ <% if @dismissible %>
23
+ <button type="button" class="<%= close_button_classes %>" data-dismiss-target="[role='alert']" aria-label="Chiudi">
24
+ <%= render BetterUi::General::IconComponent.new(name: "xmark") %>
25
+ </button>
26
+ <% end %>
27
+ </div>
@@ -0,0 +1,202 @@
1
+ module BetterUi
2
+ module Application
3
+ class AlertComponent < ViewComponent::Base
4
+ attr_reader :title, :message, :variant, :icon, :dismissible, :classes, :data, :icon_position, :outline, :rounded
5
+
6
+ # Varianti di colore disponibili
7
+ VARIANTS = {
8
+ default: {
9
+ bg: "bg-black",
10
+ border: "border-gray-900",
11
+ title: "text-white",
12
+ text: "text-white",
13
+ icon: "text-white",
14
+ close: "text-white hover:bg-gray-800"
15
+ },
16
+ white: {
17
+ bg: "bg-white",
18
+ border: "border-gray-200",
19
+ title: "text-black",
20
+ text: "text-black",
21
+ icon: "text-black",
22
+ close: "text-black hover:bg-gray-100"
23
+ },
24
+ red: {
25
+ bg: "bg-red-500",
26
+ border: "border-red-600",
27
+ title: "text-white",
28
+ text: "text-white",
29
+ icon: "text-white",
30
+ close: "text-white hover:bg-red-600"
31
+ },
32
+ rose: {
33
+ bg: "bg-rose-500",
34
+ border: "border-rose-600",
35
+ title: "text-white",
36
+ text: "text-white",
37
+ icon: "text-white",
38
+ close: "text-white hover:bg-rose-600"
39
+ },
40
+ orange: {
41
+ bg: "bg-orange-500",
42
+ border: "border-orange-600",
43
+ title: "text-white",
44
+ text: "text-white",
45
+ icon: "text-white",
46
+ close: "text-white hover:bg-orange-600"
47
+ },
48
+ green: {
49
+ bg: "bg-green-500",
50
+ border: "border-green-600",
51
+ title: "text-white",
52
+ text: "text-white",
53
+ icon: "text-white",
54
+ close: "text-white hover:bg-green-600"
55
+ },
56
+ blue: {
57
+ bg: "bg-blue-500",
58
+ border: "border-blue-600",
59
+ title: "text-white",
60
+ text: "text-white",
61
+ icon: "text-white",
62
+ close: "text-white hover:bg-blue-600"
63
+ },
64
+ yellow: {
65
+ bg: "bg-yellow-500",
66
+ border: "border-yellow-600",
67
+ title: "text-black",
68
+ text: "text-black",
69
+ icon: "text-black",
70
+ close: "text-black hover:bg-yellow-600"
71
+ },
72
+ violet: {
73
+ bg: "bg-violet-500",
74
+ border: "border-violet-600",
75
+ title: "text-white",
76
+ text: "text-white",
77
+ icon: "text-white",
78
+ close: "text-white hover:bg-violet-600"
79
+ }
80
+ }
81
+
82
+ # Icone predefinite per ciascuna variante
83
+ DEFAULT_ICONS = {
84
+ default: "bell",
85
+ white: "info-circle",
86
+ red: "exclamation-circle",
87
+ rose: "exclamation-circle",
88
+ orange: "bell",
89
+ green: "check-circle",
90
+ blue: "info-circle",
91
+ yellow: "exclamation-triangle",
92
+ violet: "shield-exclamation"
93
+ }
94
+
95
+ # Posizioni possibili per le icone
96
+ ICON_POSITIONS = [:left, :top]
97
+
98
+ # Inizializzazione del componente
99
+ def initialize(
100
+ title: nil,
101
+ message: nil,
102
+ variant: :default,
103
+ icon: nil,
104
+ dismissible: false,
105
+ classes: nil,
106
+ data: {},
107
+ icon_position: :left,
108
+ outline: false,
109
+ rounded: :sm
110
+ )
111
+ @title = title
112
+ @message = message
113
+ @variant = variant.to_sym
114
+ @icon = icon
115
+ @dismissible = dismissible
116
+ @classes = classes
117
+ @data = data
118
+ @icon_position = icon_position.to_sym
119
+ @outline = outline
120
+ @rounded = rounded.to_sym
121
+ end
122
+
123
+ # Genera l'icona in base alla variante se non specificata
124
+ def effective_icon
125
+ return @icon if @icon.present?
126
+ DEFAULT_ICONS[@variant] || DEFAULT_ICONS[:default]
127
+ end
128
+
129
+ # Genera le classi per il container
130
+ def container_classes
131
+ styles = VARIANTS.fetch(@variant, VARIANTS[:default])
132
+
133
+ base_classes = ["p-4 mb-4 flex border"]
134
+ base_classes << get_border_radius_class
135
+
136
+ [
137
+ *base_classes,
138
+ styles[:bg],
139
+ styles[:border],
140
+ @icon_position == :top ? "flex-col" : "items-start",
141
+ @classes
142
+ ].compact.join(" ")
143
+ end
144
+
145
+ # Genera il border-radius
146
+ def get_border_radius_class
147
+ ThemeHelper::BORDER_RADIUS[@rounded] || ThemeHelper::BORDER_RADIUS[:sm]
148
+ end
149
+
150
+ # Genera le classi per il titolo
151
+ def title_classes
152
+ styles = VARIANTS.fetch(@variant, VARIANTS[:default])
153
+
154
+ [
155
+ "font-medium",
156
+ styles[:title]
157
+ ].compact.join(" ")
158
+ end
159
+
160
+ # Genera le classi per il messaggio
161
+ def message_classes
162
+ styles = VARIANTS.fetch(@variant, VARIANTS[:default])
163
+
164
+ [
165
+ "mt-1",
166
+ styles[:text]
167
+ ].compact.join(" ")
168
+ end
169
+
170
+ # Genera le classi per l'icona
171
+ def icon_classes
172
+ styles = VARIANTS.fetch(@variant, VARIANTS[:default])
173
+
174
+ [
175
+ "flex-shrink-0",
176
+ @icon_position == :left ? "mr-3 mt-0.5" : "mb-3",
177
+ styles[:icon]
178
+ ].compact.join(" ")
179
+ end
180
+
181
+ # Genera le classi per il pulsante di chiusura
182
+ def close_button_classes
183
+ styles = VARIANTS.fetch(@variant, VARIANTS[:default])
184
+
185
+ [
186
+ "ml-auto -mr-1.5 -mt-1.5 inline-flex h-8 w-8 rounded-lg p-1.5 focus:ring-2 focus:ring-gray-400",
187
+ styles[:close]
188
+ ].compact.join(" ")
189
+ end
190
+
191
+ # Genera le classi per il contenuto
192
+ def content_classes
193
+ "flex-1"
194
+ end
195
+
196
+ # Verifica se il componente deve essere reso
197
+ def render?
198
+ @title.present? || @message.present? || content.present?
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,24 @@
1
+ <div class="bui-card-base <%= get_border_radius_class %> px-4 py-5 sm:p-6">
2
+ <dt class="bui-card-title text-gray-900"><%= title %></dt>
3
+ <dd class="bui-card-body mt-1 flex items-baseline justify-between md:block lg:flex">
4
+ <div class="flex items-baseline">
5
+ <span class="bui-card-value text-indigo-600"><%= value %></span>
6
+ <span class="bui-card-value-from ml-2">from <%= value_from %></span>
7
+ </div>
8
+
9
+ <div class="bui-card-trend <%= trend_classes %>">
10
+ <% if trend_type == :up %>
11
+ <svg class="mr-0.5 -ml-1 size-5 shrink-0 self-center <%= trend_icon_classes %>" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon">
12
+ <path fill-rule="evenodd" d="M10 17a.75.75 0 0 1-.75-.75V5.612L5.29 9.77a.75.75 0 0 1-1.08-1.04l5.25-5.5a.75.75 0 0 1 1.08 0l5.25 5.5a.75.75 0 1 1-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0 1 10 17Z" clip-rule="evenodd" />
13
+ </svg>
14
+ <span class="sr-only">Incremento di</span>
15
+ <% else %>
16
+ <svg class="mr-0.5 -ml-1 size-5 shrink-0 self-center <%= trend_icon_classes %>" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" data-slot="icon">
17
+ <path fill-rule="evenodd" d="M10 3a.75.75 0 0 1 .75.75v10.638l3.96-4.158a.75.75 0 1 1 1.08 1.04l-5.25 5.5a.75.75 0 0 1-1.08 0l-5.25-5.5a.75.75 0 1 1 1.08-1.04l3.96 4.158V3.75A.75.75 0 0 1 10 3Z" clip-rule="evenodd" />
18
+ </svg>
19
+ <span class="sr-only">Decremento di</span>
20
+ <% end %>
21
+ <%= trend_value %>
22
+ </div>
23
+ </dd>
24
+ </div>
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module Application
5
+ class CardComponent < ViewComponent::Base
6
+ attr_reader :title, :value, :value_from, :trend_value, :trend_type, :rounded
7
+
8
+ # Opzioni di bordi arrotondati standardizzati
9
+ CARD_RADIUS = {
10
+ none: 'bui-card-radius-none',
11
+ small: 'bui-card-radius-small',
12
+ medium: 'bui-card-radius-medium',
13
+ large: 'bui-card-radius-large',
14
+ full: 'bui-card-radius-full'
15
+ }.freeze
16
+
17
+ # @param title [String] Titolo della card
18
+ # @param value [String] Valore principale da visualizzare
19
+ # @param value_from [String] Valore precedente di riferimento
20
+ # @param trend_value [String] Percentuale di variazione
21
+ # @param trend_type [Symbol] Tipo di variazione (:up o :down)
22
+ # @param rounded [Symbol] Tipo di border-radius (:none, :small, :medium, :large, :full), default :small
23
+ def initialize(title:, value:, value_from:, trend_value:, trend_type: :up, rounded: :small)
24
+ @title = title
25
+ @value = value
26
+ @value_from = value_from
27
+ @trend_value = trend_value
28
+ @trend_type = trend_type.to_sym
29
+ @rounded = rounded.to_sym
30
+ end
31
+
32
+ def trend_classes
33
+ if trend_type == :up
34
+ 'bg-green-500 text-white'
35
+ else
36
+ 'bg-black text-white'
37
+ end
38
+ end
39
+
40
+ def trend_icon_classes
41
+ if trend_type == :up
42
+ 'text-white'
43
+ else
44
+ 'text-white'
45
+ end
46
+ end
47
+
48
+ def get_border_radius_class
49
+ CARD_RADIUS[@rounded] || CARD_RADIUS[:small]
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,8 @@
1
+ <div>
2
+ <h3 class="text-base font-semibold text-gray-900"><%= @title %></h3>
3
+ <dl class="mt-5 grid grid-cols-1 divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-sm md:grid-cols-3 md:divide-x md:divide-y-0">
4
+ <% cards.each do |card| %>
5
+ <%= card %>
6
+ <% end %>
7
+ </dl>
8
+ </div>