better_ui 0.3.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.

Potentially problematic release.


This version of better_ui might be problematic. Click here for more details.

Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +227 -209
  4. data/app/assets/javascripts/better_ui/controllers/navbar_controller.js +138 -0
  5. data/app/assets/javascripts/better_ui/controllers/sidebar_controller.js +211 -0
  6. data/app/assets/javascripts/better_ui/controllers/toast_controller.js +161 -0
  7. data/app/assets/javascripts/better_ui/index.js +159 -0
  8. data/app/assets/javascripts/better_ui/toast_manager.js +77 -0
  9. data/app/assets/stylesheets/better_ui/application.css +30 -0
  10. data/app/components/better_ui/application/alert_component.html.erb +27 -0
  11. data/app/components/better_ui/application/alert_component.rb +202 -0
  12. data/app/components/better_ui/application/card_component.html.erb +24 -0
  13. data/app/components/better_ui/application/card_component.rb +53 -0
  14. data/app/components/better_ui/application/card_container_component.html.erb +8 -0
  15. data/app/components/better_ui/application/card_container_component.rb +14 -0
  16. data/app/components/better_ui/application/header_component.html.erb +88 -0
  17. data/app/components/better_ui/application/header_component.rb +188 -0
  18. data/app/components/better_ui/application/navbar_component.html.erb +294 -0
  19. data/app/components/better_ui/application/navbar_component.rb +249 -0
  20. data/app/components/better_ui/application/sidebar_component.html.erb +207 -0
  21. data/app/components/better_ui/application/sidebar_component.rb +318 -0
  22. data/app/components/better_ui/application/toast_component.html.erb +35 -0
  23. data/app/components/better_ui/application/toast_component.rb +223 -0
  24. data/app/components/better_ui/general/avatar_component.html.erb +19 -0
  25. data/app/components/better_ui/general/avatar_component.rb +171 -0
  26. data/app/components/better_ui/general/badge_component.html.erb +19 -0
  27. data/app/components/better_ui/general/badge_component.rb +123 -0
  28. data/app/components/better_ui/general/{breadcrumb/component.html.erb → breadcrumb_component.html.erb} +4 -4
  29. data/app/components/better_ui/general/breadcrumb_component.rb +130 -0
  30. data/app/components/better_ui/general/{button/component.html.erb → button_component.html.erb} +7 -7
  31. data/app/components/better_ui/general/button_component.rb +162 -0
  32. data/app/components/better_ui/general/heading_component.html.erb +25 -0
  33. data/app/components/better_ui/general/heading_component.rb +148 -0
  34. data/app/components/better_ui/general/icon_component.html.erb +2 -0
  35. data/app/components/better_ui/general/icon_component.rb +100 -0
  36. data/app/components/better_ui/general/link_component.html.erb +17 -0
  37. data/app/components/better_ui/general/link_component.rb +132 -0
  38. data/app/components/better_ui/general/panel_component.html.erb +27 -0
  39. data/app/components/better_ui/general/panel_component.rb +103 -0
  40. data/app/components/better_ui/general/spinner_component.html.erb +15 -0
  41. data/app/components/better_ui/general/spinner_component.rb +79 -0
  42. data/app/components/better_ui/general/table_component.html.erb +73 -0
  43. data/app/components/better_ui/general/table_component.rb +167 -0
  44. data/app/components/better_ui/theme_helper.rb +171 -0
  45. data/app/controllers/better_ui/application_controller.rb +1 -0
  46. data/app/controllers/better_ui/docs_controller.rb +34 -0
  47. data/app/views/components/better_ui/general/table/_custom_body_row.html.erb +17 -0
  48. data/app/views/components/better_ui/general/table/_custom_footer_rows.html.erb +17 -0
  49. data/app/views/components/better_ui/general/table/_custom_header_rows.html.erb +12 -0
  50. data/app/views/layouts/component_preview.html.erb +32 -0
  51. data/config/initializers/lookbook.rb +12 -12
  52. data/config/routes.rb +13 -0
  53. data/lib/better_ui/engine.rb +36 -5
  54. data/lib/better_ui/version.rb +1 -1
  55. data/lib/better_ui.rb +24 -4
  56. data/lib/generators/better_ui/stylesheet_generator.rb +96 -0
  57. data/lib/generators/better_ui/templates/README +56 -0
  58. data/lib/generators/better_ui/templates/components/_avatar.scss +151 -0
  59. data/lib/generators/better_ui/templates/components/_badge.scss +142 -0
  60. data/lib/generators/better_ui/templates/components/_breadcrumb.scss +107 -0
  61. data/lib/generators/better_ui/templates/components/_button.scss +106 -0
  62. data/lib/generators/better_ui/templates/components/_card.scss +69 -0
  63. data/lib/generators/better_ui/templates/components/_heading.scss +180 -0
  64. data/lib/generators/better_ui/templates/components/_icon.scss +90 -0
  65. data/lib/generators/better_ui/templates/components/_link.scss +130 -0
  66. data/lib/generators/better_ui/templates/components/_panel.scss +144 -0
  67. data/lib/generators/better_ui/templates/components/_spinner.scss +132 -0
  68. data/lib/generators/better_ui/templates/components/_table.scss +105 -0
  69. data/lib/generators/better_ui/templates/components/_variables.scss +33 -0
  70. data/lib/generators/better_ui/templates/components_stylesheet.scss +66 -0
  71. metadata +145 -156
  72. data/app/components/better_ui/application/card/component.html.erb +0 -20
  73. data/app/components/better_ui/application/card/component.rb +0 -214
  74. data/app/components/better_ui/application/main/component.html.erb +0 -9
  75. data/app/components/better_ui/application/main/component.rb +0 -123
  76. data/app/components/better_ui/application/navbar/component.html.erb +0 -92
  77. data/app/components/better_ui/application/navbar/component.rb +0 -136
  78. data/app/components/better_ui/application/sidebar/component.html.erb +0 -227
  79. data/app/components/better_ui/application/sidebar/component.rb +0 -130
  80. data/app/components/better_ui/general/accordion/component.html.erb +0 -5
  81. data/app/components/better_ui/general/accordion/component.rb +0 -92
  82. data/app/components/better_ui/general/accordion/item_component.html.erb +0 -12
  83. data/app/components/better_ui/general/accordion/item_component.rb +0 -176
  84. data/app/components/better_ui/general/alert/component.html.erb +0 -32
  85. data/app/components/better_ui/general/alert/component.rb +0 -242
  86. data/app/components/better_ui/general/avatar/component.html.erb +0 -20
  87. data/app/components/better_ui/general/avatar/component.rb +0 -301
  88. data/app/components/better_ui/general/badge/component.html.erb +0 -23
  89. data/app/components/better_ui/general/badge/component.rb +0 -248
  90. data/app/components/better_ui/general/breadcrumb/component.rb +0 -187
  91. data/app/components/better_ui/general/button/component.rb +0 -214
  92. data/app/components/better_ui/general/divider/component.html.erb +0 -10
  93. data/app/components/better_ui/general/divider/component.rb +0 -226
  94. data/app/components/better_ui/general/dropdown/component.html.erb +0 -25
  95. data/app/components/better_ui/general/dropdown/component.rb +0 -170
  96. data/app/components/better_ui/general/dropdown/divider_component.html.erb +0 -1
  97. data/app/components/better_ui/general/dropdown/divider_component.rb +0 -41
  98. data/app/components/better_ui/general/dropdown/item_component.html.erb +0 -6
  99. data/app/components/better_ui/general/dropdown/item_component.rb +0 -119
  100. data/app/components/better_ui/general/field/component.html.erb +0 -27
  101. data/app/components/better_ui/general/field/component.rb +0 -37
  102. data/app/components/better_ui/general/heading/component.html.erb +0 -22
  103. data/app/components/better_ui/general/heading/component.rb +0 -257
  104. data/app/components/better_ui/general/icon/component.html.erb +0 -7
  105. data/app/components/better_ui/general/icon/component.rb +0 -239
  106. data/app/components/better_ui/general/input/checkbox/component.html.erb +0 -5
  107. data/app/components/better_ui/general/input/checkbox/component.rb +0 -238
  108. data/app/components/better_ui/general/input/datetime/component.html.erb +0 -5
  109. data/app/components/better_ui/general/input/datetime/component.rb +0 -223
  110. data/app/components/better_ui/general/input/radio/component.html.erb +0 -5
  111. data/app/components/better_ui/general/input/radio/component.rb +0 -230
  112. data/app/components/better_ui/general/input/select/component.html.erb +0 -16
  113. data/app/components/better_ui/general/input/select/component.rb +0 -184
  114. data/app/components/better_ui/general/input/select/select_component.html.erb +0 -5
  115. data/app/components/better_ui/general/input/select/select_component.rb +0 -37
  116. data/app/components/better_ui/general/input/text/component.html.erb +0 -5
  117. data/app/components/better_ui/general/input/text/component.rb +0 -171
  118. data/app/components/better_ui/general/input/textarea/component.html.erb +0 -5
  119. data/app/components/better_ui/general/input/textarea/component.rb +0 -166
  120. data/app/components/better_ui/general/link/component.html.erb +0 -18
  121. data/app/components/better_ui/general/link/component.rb +0 -258
  122. data/app/components/better_ui/general/modal/component.html.erb +0 -5
  123. data/app/components/better_ui/general/modal/component.rb +0 -47
  124. data/app/components/better_ui/general/modal/modal_component.html.erb +0 -52
  125. data/app/components/better_ui/general/modal/modal_component.rb +0 -160
  126. data/app/components/better_ui/general/pagination/component.html.erb +0 -85
  127. data/app/components/better_ui/general/pagination/component.rb +0 -216
  128. data/app/components/better_ui/general/panel/component.html.erb +0 -28
  129. data/app/components/better_ui/general/panel/component.rb +0 -249
  130. data/app/components/better_ui/general/progress/component.html.erb +0 -11
  131. data/app/components/better_ui/general/progress/component.rb +0 -160
  132. data/app/components/better_ui/general/spinner/component.html.erb +0 -35
  133. data/app/components/better_ui/general/spinner/component.rb +0 -93
  134. data/app/components/better_ui/general/table/component.html.erb +0 -5
  135. data/app/components/better_ui/general/table/component.rb +0 -217
  136. data/app/components/better_ui/general/table/tbody_component.html.erb +0 -3
  137. data/app/components/better_ui/general/table/tbody_component.rb +0 -30
  138. data/app/components/better_ui/general/table/td_component.html.erb +0 -3
  139. data/app/components/better_ui/general/table/td_component.rb +0 -44
  140. data/app/components/better_ui/general/table/tfoot_component.html.erb +0 -3
  141. data/app/components/better_ui/general/table/tfoot_component.rb +0 -28
  142. data/app/components/better_ui/general/table/th_component.html.erb +0 -6
  143. data/app/components/better_ui/general/table/th_component.rb +0 -51
  144. data/app/components/better_ui/general/table/thead_component.html.erb +0 -3
  145. data/app/components/better_ui/general/table/thead_component.rb +0 -28
  146. data/app/components/better_ui/general/table/tr_component.html.erb +0 -3
  147. data/app/components/better_ui/general/table/tr_component.rb +0 -30
  148. data/app/components/better_ui/general/tabs/component.html.erb +0 -11
  149. data/app/components/better_ui/general/tabs/component.rb +0 -120
  150. data/app/components/better_ui/general/tabs/panel_component.html.erb +0 -3
  151. data/app/components/better_ui/general/tabs/panel_component.rb +0 -37
  152. data/app/components/better_ui/general/tabs/tab_component.html.erb +0 -13
  153. data/app/components/better_ui/general/tabs/tab_component.rb +0 -111
  154. data/app/components/better_ui/general/tag/component.html.erb +0 -3
  155. data/app/components/better_ui/general/tag/component.rb +0 -104
  156. data/app/components/better_ui/general/tooltip/component.html.erb +0 -7
  157. data/app/components/better_ui/general/tooltip/component.rb +0 -239
  158. data/app/helpers/better_ui/application/components/card/card_helper.rb +0 -96
  159. data/app/helpers/better_ui/application/components/card.rb +0 -11
  160. data/app/helpers/better_ui/application/components/main/main_helper.rb +0 -64
  161. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +0 -77
  162. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +0 -51
  163. data/app/helpers/better_ui/application_helper.rb +0 -55
  164. data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +0 -73
  165. data/app/helpers/better_ui/general/components/accordion.rb +0 -11
  166. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +0 -57
  167. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +0 -29
  168. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +0 -53
  169. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +0 -37
  170. data/app/helpers/better_ui/general/components/button/button_helper.rb +0 -65
  171. data/app/helpers/better_ui/general/components/container/container_helper.rb +0 -60
  172. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +0 -63
  173. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +0 -32
  174. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +0 -79
  175. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +0 -62
  176. data/app/helpers/better_ui/general/components/field/field_helper.rb +0 -26
  177. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +0 -72
  178. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +0 -16
  179. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +0 -81
  180. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +0 -91
  181. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +0 -79
  182. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +0 -124
  183. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +0 -70
  184. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +0 -138
  185. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +0 -73
  186. data/app/helpers/better_ui/general/components/link/link_helper.rb +0 -89
  187. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +0 -85
  188. data/app/helpers/better_ui/general/components/modal.rb +0 -11
  189. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +0 -82
  190. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +0 -83
  191. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +0 -53
  192. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +0 -19
  193. data/app/helpers/better_ui/general/components/table/table_helper.rb +0 -53
  194. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +0 -13
  195. data/app/helpers/better_ui/general/components/table/td_helper.rb +0 -19
  196. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +0 -13
  197. data/app/helpers/better_ui/general/components/table/th_helper.rb +0 -19
  198. data/app/helpers/better_ui/general/components/table/thead_helper.rb +0 -13
  199. data/app/helpers/better_ui/general/components/table/tr_helper.rb +0 -13
  200. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +0 -62
  201. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +0 -55
  202. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +0 -95
  203. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +0 -26
  204. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +0 -60
  205. data/app/views/layouts/better_ui/application.html.erb +0 -17
  206. data/lib/better_ui/railtie.rb +0 -20
@@ -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
+ }
@@ -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
+ }