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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c59e86ef83ec560909908edbbbecd0c33a3663df87de94e99fff0475be5b23d
4
- data.tar.gz: dc7a3b578f0b2cc70f3e25c843dbc918723bf4d0b7d9bdb4b511e2ad073cc5fd
3
+ metadata.gz: 2242792aeee217a121cb73ec14ccc317d8f9c7af9e3dcdcf52629d05eca55731
4
+ data.tar.gz: 86015104cb387605208eaa8f9ffbdeb3764542fd089e09cf2dd011cecc37640b
5
5
  SHA512:
6
- metadata.gz: 66fdc7a9f14ff11b0d79caaff30d1b35ba4878dbbfcbddca7571684eb3628c62288a4fff4a33416b285c479ab96ca22ed271949184c472cb36f4131270a15552
7
- data.tar.gz: abaf0df7af66450518ab1620ca0fe165e411c2b96a6accfc3a49acb91a93b82c64c7093bacbb7e5e272040275b96063dee0253608a08f406d2e1122ff78cb8c3
6
+ metadata.gz: df1b73b3f9c3782a562ef7b585fd9bbac50c45fc8cc9661fe6ff8aa335c53f988c65ce4ab430c7c6dc82d74419b9dd80e026ad50c4533742b7940153fba2e653
7
+ data.tar.gz: 1010406d6a425b0ca48e1acf0af262634a7f5babf54edd34dfd6c7b6943dbdb1a18c250d800f68b9e41ec7bc94d095a8ed9d3e3ca7efb540bbb3ddbe4d310105
data/README.md CHANGED
@@ -14,6 +14,7 @@ BetterUI è una gemma Rails che fornisce componenti UI riutilizzabili con docume
14
14
  - **Documentazione integrata** - Visualizza esempi e documentazione direttamente nel browser
15
15
  - **Altamente personalizzabile** - Adatta facilmente lo stile al tuo brand
16
16
  - **Componenti modulari** - Usa solo ciò di cui hai bisogno
17
+ - **Preview dei componenti** - Visualizza e interagisci con i componenti usando Lookbook
17
18
 
18
19
  ## 📦 Componenti disponibili
19
20
 
@@ -24,6 +25,10 @@ BetterUI è una gemma Rails che fornisce componenti UI riutilizzabili con docume
24
25
  | **Card** | Contenitori flessibili con header, body e footer |
25
26
  | **Modal** | Finestre di dialogo modali |
26
27
  | **Tabs** | Navigazione a schede |
28
+ | **Navbar** | Barra di navigazione responsive con supporto per menu dropdown |
29
+ | **Sidebar** | Menu laterale con supporto per navigazione gerarchica |
30
+ | **Toast** | Notifiche temporanee con timer e animazioni |
31
+ | **Header** | Intestazioni di pagina con titolo, sottotitolo, breadcrumb e azioni |
27
32
  | **Form Elements** | Campi di input stilizzati (in arrivo) |
28
33
 
29
34
  ## 🚀 Installazione
@@ -82,11 +87,70 @@ Una volta installato, puoi iniziare ad usare i componenti:
82
87
  <p>Contenuto della card...</p>
83
88
  <% end %>
84
89
  <% end %>
90
+
91
+ <%# Header con breadcrumb %>
92
+ <%= render BetterUi::Application::HeaderComponent.new(
93
+ title: "Dashboard",
94
+ subtitle: "Gestisci tutto da qui",
95
+ breadcrumbs: [
96
+ { text: "Home", url: "/" },
97
+ { text: "Dashboard" }
98
+ ],
99
+ actions: [
100
+ { content: button_html("Nuovo", "primary") }
101
+ ]
102
+ ) %>
85
103
  ```
86
104
 
87
105
  Visita `/better_ui` nella tua applicazione per vedere la documentazione completa e gli esempi.
88
106
 
89
- ## 🎨 Personalizzazione
107
+ ### Il componente Header
108
+
109
+ Il componente Header è progettato per creare intestazioni di pagina complete con numerose funzionalità:
110
+
111
+ ```erb
112
+ <%= render BetterUi::Application::HeaderComponent.new(
113
+ title: {
114
+ text: "Impostazioni",
115
+ icon: "settings"
116
+ },
117
+ subtitle: "Configura le preferenze del sistema",
118
+ breadcrumbs: [
119
+ { text: "Home", url: "/" },
120
+ { text: "Admin", url: "/admin" },
121
+ { text: "Impostazioni" }
122
+ ],
123
+ variant: :modern,
124
+ fixed: :top,
125
+ show_breadcrumbs: true,
126
+ actions: [
127
+ { content: button_html("Salva", "primary") },
128
+ { content: button_html("Annulla", "secondary") }
129
+ ]
130
+ ) %>
131
+ ```
132
+
133
+ Il componente supporta:
134
+ - Titolo con opzionale icona integrata
135
+ - Sottotitolo descrittivo
136
+ - Breadcrumbs completi con link di navigazione
137
+ - Azioni contestuali (pulsanti, menu, ecc.)
138
+ - Varianti di stile: modern, light, dark, primary, transparent
139
+ - Posizionamento fisso (in alto o in basso)
140
+ - Controllo della visibilità dei breadcrumb
141
+
142
+ ### Preview dei componenti con Lookbook
143
+
144
+ BetterUI integra [Lookbook](https://github.com/allmarkedup/lookbook) per visualizzare in anteprima i componenti UI:
145
+
146
+ 1. Accedi a `/better_ui/lookbook` nella tua applicazione per visualizzare il catalogo componenti
147
+ 2. Esplora le varianti e le configurazioni disponibili per ogni componente
148
+ 3. Visualizza il codice sorgente e il markup generato
149
+ 4. Interagisci con i componenti in tempo reale
150
+
151
+ Lookbook è disponibile solo negli ambienti di sviluppo e test.
152
+
153
+ ## 🎮 Personalizzazione
90
154
 
91
155
  ### Usa l'inizializzatore
92
156
 
@@ -0,0 +1,138 @@
1
+ // Navbar controller per gestire il comportamento del menu mobile e dei dropdown
2
+ import { Controller } from "@hotwired/stimulus"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["menu", "dropdown", "submenu"]
6
+
7
+ connect() {
8
+ // Verifica se siamo su mobile e aggiorna lo stato del menu
9
+ this.updateMenuState();
10
+
11
+ // Aggiungi un event listener per il resize della finestra
12
+ window.addEventListener("resize", this.updateMenuState.bind(this));
13
+
14
+ // Chiudi menu quando si clicca su un link (solo su mobile)
15
+ if (this.hasMenuTarget) {
16
+ const links = this.menuTarget.querySelectorAll("a");
17
+ links.forEach(link => {
18
+ link.addEventListener("click", () => {
19
+ // Se siamo su mobile, chiudi il menu
20
+ if (window.innerWidth < 768) {
21
+ this.closeMenu();
22
+ }
23
+ });
24
+ });
25
+ }
26
+
27
+ // Aggiungi listener per i click all'esterno del menu
28
+ document.addEventListener("click", this.handleClickOutside.bind(this))
29
+ }
30
+
31
+ disconnect() {
32
+ // Rimuovi event listener per il resize della finestra
33
+ window.removeEventListener("resize", this.updateMenuState.bind(this));
34
+
35
+ // Rimuovi listener al disconnette
36
+ document.removeEventListener("click", this.handleClickOutside.bind(this))
37
+ }
38
+
39
+ // Metodo per alternare l'apertura/chiusura del menu
40
+ toggleMenu(event) {
41
+ event.stopPropagation()
42
+ const isExpanded = this.menuTarget.classList.contains("hidden") === false
43
+
44
+ if (isExpanded) {
45
+ this.closeMenu()
46
+ } else {
47
+ this.openMenu()
48
+ }
49
+ }
50
+
51
+ // Metodo per chiudere il menu
52
+ closeMenu() {
53
+ this.menuTarget.classList.add("hidden")
54
+
55
+ // Aggiorna l'attributo aria-expanded
56
+ const button = this.element.querySelector("[aria-controls='navbar-menu']")
57
+ if (button) {
58
+ button.setAttribute("aria-expanded", "false")
59
+ }
60
+ }
61
+
62
+ // Metodo per aggiornare lo stato del menu in base alla dimensione della finestra
63
+ updateMenuState() {
64
+ if (this.hasMenuTarget) {
65
+ // Se siamo su desktop (md breakpoint - 768px)
66
+ if (window.innerWidth >= 768) {
67
+ // Assicurati che il menu sia visibile su desktop
68
+ this.menuTarget.classList.remove("hidden");
69
+ this.menuTarget.classList.add("md:block");
70
+ } else {
71
+ // Su mobile, nascondi il menu di default
72
+ this.menuTarget.classList.add("hidden");
73
+ }
74
+
75
+ // Aggiorna l'attributo aria-expanded
76
+ const button = this.element.querySelector("[aria-controls='navbar-menu']");
77
+ if (button) {
78
+ const isExpanded = window.innerWidth >= 768 ? "true" : "false";
79
+ button.setAttribute("aria-expanded", isExpanded);
80
+ }
81
+ }
82
+ }
83
+
84
+ openMenu() {
85
+ this.menuTarget.classList.remove("hidden")
86
+
87
+ // Chiudi tutti i dropdown nel menu mobile
88
+ if (this.hasSubmenuTarget) {
89
+ this.submenuTargets.forEach(submenu => {
90
+ submenu.classList.add("hidden")
91
+ })
92
+ }
93
+
94
+ // Aggiorna stato del pulsante
95
+ event.currentTarget.setAttribute("aria-expanded", "true")
96
+ }
97
+
98
+ toggleDropdown(event) {
99
+ event.stopPropagation()
100
+ const button = event.currentTarget
101
+ const dropdownId = button.getAttribute("aria-controls")
102
+ const dropdown = document.getElementById(dropdownId)
103
+
104
+ if (dropdown) {
105
+ const isExpanded = button.getAttribute("aria-expanded") === "true"
106
+
107
+ if (isExpanded) {
108
+ dropdown.classList.add("hidden")
109
+ button.setAttribute("aria-expanded", "false")
110
+ } else {
111
+ // Chiudi tutti gli altri dropdown prima di aprire quello corrente
112
+ if (this.hasSubmenuTarget) {
113
+ this.submenuTargets.forEach(submenu => {
114
+ if (submenu.id !== dropdownId) {
115
+ submenu.classList.add("hidden")
116
+
117
+ // Trova e aggiorna il pulsante associato
118
+ const associatedButton = this.element.querySelector(`[aria-controls='${submenu.id}']`)
119
+ if (associatedButton) {
120
+ associatedButton.setAttribute("aria-expanded", "false")
121
+ }
122
+ }
123
+ })
124
+ }
125
+
126
+ dropdown.classList.remove("hidden")
127
+ button.setAttribute("aria-expanded", "true")
128
+ }
129
+ }
130
+ }
131
+
132
+ handleClickOutside(event) {
133
+ // Chiudi il menu se si fa clic all'esterno
134
+ if (this.element.contains(event.target) === false) {
135
+ this.closeMenu()
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,211 @@
1
+ // Sidebar controller per gestire il comportamento del menu laterale
2
+ import { Controller } from "@hotwired/stimulus"
3
+
4
+ export default class extends Controller {
5
+ static targets = ["container", "overlay", "toggleButton", "toggleIcon", "dropdown", "submenu", "chevron"]
6
+ static values = {
7
+ collapsed: { type: Boolean, default: false },
8
+ position: { type: String, default: "left" },
9
+ overlayOnMobile: { type: Boolean, default: true }
10
+ }
11
+
12
+ connect() {
13
+ // Applica lo stato iniziale
14
+ if (this.collapsedValue) {
15
+ this.collapse();
16
+ } else {
17
+ // Assicuriamo che la sidebar sia espansa all'inizio
18
+ this.containerTarget.style.transform = "translateX(0)";
19
+ }
20
+
21
+ // Imposta i listener per il ridimensionamento della finestra
22
+ window.addEventListener("resize", this.handleResize.bind(this));
23
+
24
+ // Gestisci lo stato iniziale in base alla dimensione della finestra
25
+ this.handleResize();
26
+
27
+ // Trova e apri i sottomenu con elementi attivi
28
+ this.openSubmenuWithActiveItems();
29
+ }
30
+
31
+ disconnect() {
32
+ // Rimuovi i listener all'uscita
33
+ window.removeEventListener("resize", this.handleResize.bind(this));
34
+ }
35
+
36
+ // Toggle dell'intera sidebar
37
+ toggle() {
38
+ if (this.isCollapsed()) {
39
+ this.expand();
40
+ } else {
41
+ this.collapse();
42
+ }
43
+ }
44
+
45
+ // Espandi la sidebar
46
+ expand() {
47
+ // Mostra la sidebar completa
48
+ this.containerTarget.classList.remove("transform-translate");
49
+ this.containerTarget.style.transform = "translateX(0)";
50
+
51
+ // Ruota l'icona del toggle button
52
+ if (this.hasToggleIconTarget) {
53
+ if (this.positionValue === "left") {
54
+ this.toggleIconTarget.style.transform = "rotate(0deg)";
55
+ } else {
56
+ this.toggleIconTarget.style.transform = "rotate(0deg)";
57
+ }
58
+ }
59
+
60
+ // Aggiorna lo stato
61
+ this.collapsedValue = false;
62
+ }
63
+
64
+ // Contrai la sidebar
65
+ collapse() {
66
+ const width = this.containerTarget.offsetWidth;
67
+ // Usa un valore più piccolo per mantenere visibile una parte della sidebar
68
+ const translateValue = this.positionValue === "left" ? `-${width - 10}px` : `${width - 10}px`;
69
+
70
+ // Nascondi la sidebar
71
+ this.containerTarget.classList.add("transform-translate");
72
+ this.containerTarget.style.transform = `translateX(${translateValue})`;
73
+
74
+ // Ruota l'icona del toggle button
75
+ if (this.hasToggleIconTarget) {
76
+ if (this.positionValue === "left") {
77
+ this.toggleIconTarget.style.transform = "rotate(180deg)";
78
+ } else {
79
+ this.toggleIconTarget.style.transform = "rotate(180deg)";
80
+ }
81
+ }
82
+
83
+ // Aggiorna lo stato
84
+ this.collapsedValue = true;
85
+ }
86
+
87
+ // Apri la sidebar
88
+ open() {
89
+ // Mostra la sidebar
90
+ this.containerTarget.style.transform = "translateX(0)";
91
+
92
+ // Mostra l'overlay se necessario
93
+ if (this.overlayOnMobileValue && window.innerWidth < 768) {
94
+ this.overlayTarget.classList.remove("hidden");
95
+ this.overlayTarget.classList.add("opacity-100");
96
+ document.body.classList.add("overflow-hidden");
97
+ }
98
+
99
+ // Aggiorna lo stato
100
+ this.collapsedValue = false;
101
+ }
102
+
103
+ // Chiudi la sidebar
104
+ close() {
105
+ // Se è già contratta su desktop, non fare nulla
106
+ if (this.collapsedValue && window.innerWidth >= 768) {
107
+ return;
108
+ }
109
+
110
+ // Su mobile, nascondi completamente
111
+ if (window.innerWidth < 768) {
112
+ const width = this.containerTarget.offsetWidth;
113
+ const translateValue = this.positionValue === "left" ? `-${width}px` : `${width}px`;
114
+ this.containerTarget.style.transform = `translateX(${translateValue})`;
115
+
116
+ // Nascondi l'overlay
117
+ this.overlayTarget.classList.add("hidden");
118
+ this.overlayTarget.classList.remove("opacity-100");
119
+ document.body.classList.remove("overflow-hidden");
120
+ } else {
121
+ // Su desktop, contrai
122
+ this.collapse();
123
+ }
124
+ }
125
+
126
+ // Verifica se la sidebar è contratta
127
+ isCollapsed() {
128
+ return this.collapsedValue;
129
+ }
130
+
131
+ // Handler per il ridimensionamento della finestra
132
+ handleResize() {
133
+ // Su mobile, mostra l'overlay se la sidebar è aperta
134
+ if (window.innerWidth < 768) {
135
+ const isHidden = this.containerTarget.style.transform.includes("translateX");
136
+
137
+ if (!isHidden && this.overlayOnMobileValue) {
138
+ this.overlayTarget.classList.remove("hidden");
139
+ } else {
140
+ this.overlayTarget.classList.add("hidden");
141
+ }
142
+ } else {
143
+ // Su desktop, nascondi l'overlay
144
+ this.overlayTarget.classList.add("hidden");
145
+ document.body.classList.remove("overflow-hidden");
146
+
147
+ // Ripristina lo stato della sidebar in base al valore di collapsed
148
+ if (this.collapsedValue) {
149
+ this.collapse();
150
+ } else {
151
+ this.expand();
152
+ }
153
+ }
154
+ }
155
+
156
+ // Toggle di un sottomenu
157
+ toggleSubmenu(event) {
158
+ const button = event.currentTarget;
159
+ const submenuId = button.getAttribute("aria-controls");
160
+ const submenu = document.getElementById(submenuId);
161
+ const chevron = button.querySelector("[data-sidebar-target='chevron']");
162
+
163
+ if (submenu) {
164
+ const isExpanded = button.getAttribute("aria-expanded") === "true";
165
+
166
+ if (isExpanded) {
167
+ // Chiudi il sottomenu
168
+ submenu.classList.add("hidden");
169
+ button.setAttribute("aria-expanded", "false");
170
+ if (chevron) {
171
+ chevron.querySelector("svg").style.transform = "rotate(0deg)";
172
+ }
173
+ } else {
174
+ // Apri il sottomenu
175
+ submenu.classList.remove("hidden");
176
+ button.setAttribute("aria-expanded", "true");
177
+ if (chevron) {
178
+ chevron.querySelector("svg").style.transform = "rotate(180deg)";
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ // Apri automaticamente i sottomenu che contengono elementi attivi
185
+ openSubmenuWithActiveItems() {
186
+ // Trova tutti i dropdown
187
+ if (this.hasDropdownTarget) {
188
+ this.dropdownTargets.forEach(dropdown => {
189
+ const submenuId = dropdown.getAttribute("aria-controls");
190
+ const submenu = document.getElementById(submenuId);
191
+
192
+ if (submenu) {
193
+ // Verifica se il sottomenu contiene elementi attivi
194
+ const activeItems = submenu.querySelectorAll(".bg-gray-100, .bg-gray-700, .bg-orange-700, .bg-blue-700");
195
+
196
+ if (activeItems.length > 0) {
197
+ // Apri il sottomenu
198
+ submenu.classList.remove("hidden");
199
+ dropdown.setAttribute("aria-expanded", "true");
200
+
201
+ // Ruota l'icona chevron se presente
202
+ const chevron = dropdown.querySelector("[data-sidebar-target='chevron']");
203
+ if (chevron) {
204
+ chevron.querySelector("svg").style.transform = "rotate(180deg)";
205
+ }
206
+ }
207
+ }
208
+ });
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,161 @@
1
+ // Toast controller per gestire il comportamento delle notifiche toast
2
+ import { Controller } from "@hotwired/stimulus"
3
+ import ToastManager from "../toast_manager"
4
+
5
+ export default class extends Controller {
6
+ static targets = ["progressBar"]
7
+ static values = {
8
+ duration: { type: Number, default: 5000 },
9
+ autoHide: { type: Boolean, default: true },
10
+ position: { type: String, default: "top-right" }
11
+ }
12
+
13
+ connect() {
14
+ // Estrai la posizione dalle classi del toast o utilizza il valore predefinito
15
+ this.position = this.getPositionFromClasses() || this.positionValue;
16
+
17
+ // Rimuovi le classi di posizione originali per evitare conflitti
18
+ this.removePositionClasses();
19
+
20
+ // Registra il toast con il manager e ottieni la funzione di pulizia
21
+ this.cleanupFunction = ToastManager.registerToast(this.element, this.position);
22
+
23
+ // Applica l'animazione di entrata
24
+ this.element.classList.add("opacity-0", "translate-y-2");
25
+
26
+ // Permetti al DOM di essere aggiornato prima di mostrare
27
+ setTimeout(() => {
28
+ this.element.classList.remove("opacity-0", "translate-y-2");
29
+ this.element.classList.add("opacity-100", "translate-y-0");
30
+ }, 10);
31
+
32
+ // Se auto-hide è attivato, imposta il timer
33
+ if (this.autoHideValue) {
34
+ this.startProgressBar();
35
+ this.hideTimeout = setTimeout(() => {
36
+ this.hide();
37
+ }, this.durationValue);
38
+ }
39
+
40
+ // Aggiungi eventi per pausa/ripresa dell'auto-hide
41
+ this.element.addEventListener("mouseenter", this.mouseEnter.bind(this));
42
+ this.element.addEventListener("mouseleave", this.mouseLeave.bind(this));
43
+ }
44
+
45
+ // Estrae la posizione dalle classi del toast
46
+ getPositionFromClasses() {
47
+ const positionClasses = [
48
+ "top-right", "top-left", "bottom-right", "bottom-left", "top-center", "bottom-center"
49
+ ];
50
+
51
+ for (const position of positionClasses) {
52
+ if (this.element.classList.contains(position)) {
53
+ return position;
54
+ }
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ // Rimuove le classi di posizione originali
61
+ removePositionClasses() {
62
+ const positionClasses = [
63
+ "top-4", "right-4", "bottom-4", "left-4", "left-1/2", "transform", "-translate-x-1/2"
64
+ ];
65
+
66
+ positionClasses.forEach(cls => {
67
+ this.element.classList.remove(cls);
68
+ });
69
+ }
70
+
71
+ startProgressBar() {
72
+ if (this.hasProgressBarTarget) {
73
+ this.progressBarTarget.style.width = "100%";
74
+
75
+ // Imposta una transizione che dura quanto il timeout
76
+ this.progressBarTarget.style.transition = `width ${this.durationValue}ms linear`;
77
+
78
+ // Forza un reflow per assicurare che la transizione inizi
79
+ void this.progressBarTarget.offsetWidth;
80
+
81
+ // Inizia la transizione
82
+ this.progressBarTarget.style.width = "0%";
83
+ }
84
+ }
85
+
86
+ disconnect() {
87
+ if (this.hideTimeout) {
88
+ clearTimeout(this.hideTimeout);
89
+ }
90
+
91
+ // Rimuovi gli event listener
92
+ this.element.removeEventListener("mouseenter", this.mouseEnter.bind(this));
93
+ this.element.removeEventListener("mouseleave", this.mouseLeave.bind(this));
94
+
95
+ // Esegui la funzione di pulizia per rimuovere il toast dal manager
96
+ if (this.cleanupFunction) {
97
+ this.cleanupFunction();
98
+ }
99
+ }
100
+
101
+ hide(event) {
102
+ if (event) {
103
+ event.preventDefault();
104
+ }
105
+
106
+ // Applica l'animazione di uscita
107
+ this.element.classList.remove("opacity-100", "translate-y-0");
108
+ this.element.classList.add("opacity-0", "translate-y-2");
109
+
110
+ // Rimuovi l'elemento dopo l'animazione
111
+ setTimeout(() => {
112
+ this.element.remove();
113
+ }, 300);
114
+ }
115
+
116
+ // Metodo per pausa e ripresa dell'autoHide quando l'utente interagisce con il toast
117
+ pauseAutoHide() {
118
+ if (this.hideTimeout) {
119
+ clearTimeout(this.hideTimeout);
120
+
121
+ if (this.hasProgressBarTarget) {
122
+ // Salva la larghezza corrente per riprendere da lì
123
+ const computedStyle = window.getComputedStyle(this.progressBarTarget);
124
+ this.pausedWidth = computedStyle.width;
125
+ this.progressBarTarget.style.transition = "none";
126
+ this.progressBarTarget.style.width = this.pausedWidth;
127
+ }
128
+ }
129
+ }
130
+
131
+ resumeAutoHide() {
132
+ if (this.autoHideValue) {
133
+ // Calcola il tempo rimanente basato sulla larghezza della barra di progresso
134
+ let remainingTime = this.durationValue;
135
+
136
+ if (this.hasProgressBarTarget && this.pausedWidth) {
137
+ const percentage = parseFloat(this.pausedWidth) / parseFloat(window.getComputedStyle(this.progressBarTarget.parentElement).width);
138
+ remainingTime = this.durationValue * percentage;
139
+ }
140
+
141
+ this.hideTimeout = setTimeout(() => {
142
+ this.hide();
143
+ }, remainingTime);
144
+
145
+ if (this.hasProgressBarTarget && this.pausedWidth) {
146
+ this.progressBarTarget.style.transition = `width ${remainingTime}ms linear`;
147
+ void this.progressBarTarget.offsetWidth;
148
+ this.progressBarTarget.style.width = "0%";
149
+ }
150
+ }
151
+ }
152
+
153
+ // Gestori eventi per pausa/ripresa dell'autoHide
154
+ mouseEnter() {
155
+ this.pauseAutoHide();
156
+ }
157
+
158
+ mouseLeave() {
159
+ this.resumeAutoHide();
160
+ }
161
+ }