better_ui 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -1
  3. data/app/assets/javascripts/better_ui/controllers/navbar_controller.js +138 -0
  4. data/app/assets/javascripts/better_ui/controllers/sidebar_controller.js +211 -0
  5. data/app/assets/javascripts/better_ui/controllers/toast_controller.js +161 -0
  6. data/app/assets/javascripts/better_ui/index.js +159 -0
  7. data/app/assets/javascripts/better_ui/toast_manager.js +77 -0
  8. data/app/assets/stylesheets/better_ui/application.css +25 -351
  9. data/app/components/better_ui/application/alert_component.html.erb +27 -0
  10. data/app/components/better_ui/application/alert_component.rb +196 -0
  11. data/app/components/better_ui/application/header_component.html.erb +88 -0
  12. data/app/components/better_ui/application/header_component.rb +188 -0
  13. data/app/components/better_ui/application/navbar_component.html.erb +294 -0
  14. data/app/components/better_ui/application/navbar_component.rb +249 -0
  15. data/app/components/better_ui/application/sidebar_component.html.erb +207 -0
  16. data/app/components/better_ui/application/sidebar_component.rb +318 -0
  17. data/app/components/better_ui/application/toast_component.html.erb +35 -0
  18. data/app/components/better_ui/application/toast_component.rb +188 -0
  19. data/app/components/better_ui/general/breadcrumb_component.html.erb +39 -0
  20. data/app/components/better_ui/general/breadcrumb_component.rb +132 -0
  21. data/app/components/better_ui/general/button_component.html.erb +34 -0
  22. data/app/components/better_ui/general/button_component.rb +193 -0
  23. data/app/components/better_ui/general/heading_component.html.erb +25 -0
  24. data/app/components/better_ui/general/heading_component.rb +142 -0
  25. data/app/components/better_ui/general/icon_component.html.erb +2 -0
  26. data/app/components/better_ui/general/icon_component.rb +101 -0
  27. data/app/components/better_ui/general/panel_component.html.erb +27 -0
  28. data/app/components/better_ui/general/panel_component.rb +97 -0
  29. data/app/components/better_ui/general/table_component.html.erb +37 -0
  30. data/app/components/better_ui/general/table_component.rb +141 -0
  31. data/app/components/better_ui/theme_helper.rb +169 -0
  32. data/app/controllers/better_ui/application_controller.rb +1 -0
  33. data/app/controllers/better_ui/docs_controller.rb +18 -25
  34. data/app/helpers/better_ui_application_helper.rb +99 -0
  35. data/app/views/layouts/component_preview.html.erb +32 -0
  36. data/config/initializers/lookbook.rb +23 -0
  37. data/config/routes.rb +6 -1
  38. data/lib/better_ui/engine.rb +24 -1
  39. data/lib/better_ui/version.rb +1 -1
  40. metadata +103 -7
  41. data/app/helpers/better_ui/application_helper.rb +0 -183
  42. data/app/views/better_ui/docs/component.html.erb +0 -365
  43. data/app/views/better_ui/docs/index.html.erb +0 -100
  44. data/app/views/better_ui/docs/show.html.erb +0 -60
  45. data/app/views/layouts/better_ui/application.html.erb +0 -135
@@ -0,0 +1,159 @@
1
+ // Entry point per tutti i componenti JavaScript di Better UI
2
+ import { Application } from "@hotwired/stimulus"
3
+ import ToastController from "./controllers/toast_controller"
4
+ import NavbarController from "./controllers/navbar_controller"
5
+ import SidebarController from "./controllers/sidebar_controller"
6
+
7
+ // Configura Stimulus
8
+ const application = Application.start()
9
+
10
+ // Registra i controller
11
+ application.register("toast", ToastController)
12
+ application.register("navbar", NavbarController)
13
+ application.register("sidebar", SidebarController)
14
+
15
+ // Esporta i controller e altri componenti
16
+ export { ToastController, NavbarController, SidebarController }
17
+ export { default as ToastManager } from "./toast_manager"
18
+
19
+ // Funzione di utilità per creare e mostrare un toast programmaticamente
20
+ export function showToast(options = {}) {
21
+ const {
22
+ title = null,
23
+ message = "Notifica",
24
+ variant = "info",
25
+ position = "top-right",
26
+ duration = 5000,
27
+ dismissible = true,
28
+ autoHide = true,
29
+ icon = null
30
+ } = options;
31
+
32
+ // Crea l'elemento toast
33
+ const toast = document.createElement("div");
34
+ toast.setAttribute("role", "status");
35
+ toast.setAttribute("aria-live", "polite");
36
+ toast.classList.add(
37
+ "fixed", "z-50", "rounded-lg", "p-4", "border", "shadow-lg",
38
+ "transform", "transition-transform", "duration-300",
39
+ "min-w-[20rem]", "max-w-sm", "flex", "items-start",
40
+ position
41
+ );
42
+
43
+ // Aggiungi classi specifiche per la variante
44
+ const variantClasses = {
45
+ primary: ["bg-orange-50", "border-orange-300"],
46
+ info: ["bg-blue-50", "border-blue-300"],
47
+ success: ["bg-green-50", "border-green-300"],
48
+ warning: ["bg-yellow-50", "border-yellow-300"],
49
+ danger: ["bg-red-50", "border-red-300"],
50
+ dark: ["bg-gray-800", "border-gray-700"]
51
+ };
52
+
53
+ // Applica le classi della variante
54
+ const selectedVariant = variantClasses[variant] || variantClasses.info;
55
+ toast.classList.add(...selectedVariant);
56
+
57
+ // Aggiungi attributi per Stimulus
58
+ toast.setAttribute("data-controller", "toast");
59
+ toast.setAttribute("data-toast-duration-value", duration);
60
+ toast.setAttribute("data-toast-auto-hide-value", autoHide);
61
+ toast.setAttribute("data-toast-position-value", position);
62
+
63
+ // Costruisci il contenuto HTML
64
+ let html = "";
65
+
66
+ // Icona
67
+ const defaultIcons = {
68
+ primary: "bell",
69
+ info: "info-circle",
70
+ success: "check-circle",
71
+ warning: "exclamation-triangle",
72
+ danger: "exclamation-circle",
73
+ dark: "shield-exclamation"
74
+ };
75
+
76
+ const iconName = icon || defaultIcons[variant] || defaultIcons.info;
77
+
78
+ if (iconName) {
79
+ const iconColorClasses = {
80
+ primary: "text-orange-500",
81
+ info: "text-blue-500",
82
+ success: "text-green-500",
83
+ warning: "text-yellow-500",
84
+ danger: "text-red-500",
85
+ dark: "text-gray-400"
86
+ };
87
+
88
+ html += `
89
+ <div class="flex-shrink-0 mr-3 mt-0.5 ${iconColorClasses[variant] || iconColorClasses.info}">
90
+ <i class="fas fa-${iconName}"></i>
91
+ </div>
92
+ `;
93
+ }
94
+
95
+ // Contenuto
96
+ const titleColorClasses = {
97
+ primary: "text-orange-800",
98
+ info: "text-blue-800",
99
+ success: "text-green-800",
100
+ warning: "text-yellow-800",
101
+ danger: "text-red-800",
102
+ dark: "text-white"
103
+ };
104
+
105
+ const messageColorClasses = {
106
+ primary: "text-orange-700",
107
+ info: "text-blue-700",
108
+ success: "text-green-700",
109
+ warning: "text-yellow-700",
110
+ danger: "text-red-700",
111
+ dark: "text-gray-300"
112
+ };
113
+
114
+ html += '<div class="flex-1">';
115
+
116
+ if (title) {
117
+ html += `<div class="font-medium ${titleColorClasses[variant] || titleColorClasses.info}">${title}</div>`;
118
+ }
119
+
120
+ if (message) {
121
+ html += `<div class="mt-1 ${messageColorClasses[variant] || messageColorClasses.info}">${message}</div>`;
122
+ }
123
+
124
+ if (autoHide) {
125
+ html += `
126
+ <div class="w-full bg-gray-200 h-1 mt-2 rounded overflow-hidden">
127
+ <div class="bg-current h-1 transition-all" data-toast-target="progressBar"></div>
128
+ </div>
129
+ `;
130
+ }
131
+
132
+ html += '</div>';
133
+
134
+ // Pulsante di chiusura
135
+ if (dismissible) {
136
+ const closeButtonColorClasses = {
137
+ primary: "text-orange-500 hover:bg-orange-100",
138
+ info: "text-blue-500 hover:bg-blue-100",
139
+ success: "text-green-500 hover:bg-green-100",
140
+ warning: "text-yellow-500 hover:bg-yellow-100",
141
+ danger: "text-red-500 hover:bg-red-100",
142
+ dark: "text-gray-400 hover:bg-gray-700"
143
+ };
144
+
145
+ html += `
146
+ <button type="button" class="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 ${closeButtonColorClasses[variant] || closeButtonColorClasses.info}" data-action="toast#hide" aria-label="Chiudi">
147
+ <i class="fas fa-xmark"></i>
148
+ </button>
149
+ `;
150
+ }
151
+
152
+ // Inserisci il contenuto
153
+ toast.innerHTML = html;
154
+
155
+ // Aggiungi il toast al documento
156
+ document.body.appendChild(toast);
157
+
158
+ return toast;
159
+ }
@@ -0,0 +1,77 @@
1
+ // Gestore per l'impilamento dei toast in diverse posizioni
2
+ class ToastManager {
3
+ constructor() {
4
+ // Mantiene traccia dei toast attivi per posizione
5
+ this.toasts = {
6
+ 'top-right': [],
7
+ 'top-left': [],
8
+ 'bottom-right': [],
9
+ 'bottom-left': [],
10
+ 'top-center': [],
11
+ 'bottom-center': []
12
+ }
13
+
14
+ // Offset di base per ogni posizione (in px)
15
+ this.baseOffset = 16; // equivalente a Tailwind top-4 (16px)
16
+
17
+ // Spaziatura tra i toast (in px)
18
+ this.spacing = 12;
19
+ }
20
+
21
+ // Registra un nuovo toast e calcola la sua posizione
22
+ registerToast(toast, position) {
23
+ if (!this.toasts[position]) {
24
+ console.warn(`Posizione toast non valida: ${position}. Utilizzo 'top-right' come predefinita.`);
25
+ position = 'top-right';
26
+ }
27
+
28
+ // Aggiungi il toast all'array della posizione specifica
29
+ this.toasts[position].push(toast);
30
+
31
+ // Ricalcola le posizioni per tutti i toast in questa posizione
32
+ this.updatePositions(position);
33
+
34
+ return () => this.removeToast(toast, position);
35
+ }
36
+
37
+ // Rimuove un toast e ricalcola le posizioni
38
+ removeToast(toast, position) {
39
+ const index = this.toasts[position].indexOf(toast);
40
+ if (index !== -1) {
41
+ this.toasts[position].splice(index, 1);
42
+ this.updatePositions(position);
43
+ }
44
+ }
45
+
46
+ // Aggiorna le posizioni di tutti i toast in una data posizione
47
+ updatePositions(position) {
48
+ const isTopPosition = position.startsWith('top-');
49
+ const toastArray = this.toasts[position];
50
+
51
+ let currentOffset = this.baseOffset;
52
+
53
+ toastArray.forEach((toast, index) => {
54
+ // Applica la posizione corretta
55
+ if (isTopPosition) {
56
+ toast.style.top = `${currentOffset}px`;
57
+ } else {
58
+ toast.style.bottom = `${currentOffset}px`;
59
+ }
60
+
61
+ // Aggiorna l'offset per il prossimo toast
62
+ const height = toast.offsetHeight;
63
+ currentOffset += height + this.spacing;
64
+ });
65
+ }
66
+
67
+ // Metodo statico per accedere all'istanza singleton
68
+ static getInstance() {
69
+ if (!ToastManager.instance) {
70
+ ToastManager.instance = new ToastManager();
71
+ }
72
+ return ToastManager.instance;
73
+ }
74
+ }
75
+
76
+ // Esporta il gestore come singleton
77
+ export default ToastManager.getInstance();
@@ -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>