better_ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +225 -119
  4. data/app/assets/stylesheets/better_ui/application.css +0 -356
  5. data/app/components/better_ui/application/card/component.html.erb +20 -0
  6. data/app/components/better_ui/application/card/component.rb +214 -0
  7. data/app/components/better_ui/application/main/component.html.erb +9 -0
  8. data/app/components/better_ui/application/main/component.rb +123 -0
  9. data/app/components/better_ui/application/navbar/component.html.erb +92 -0
  10. data/app/components/better_ui/application/navbar/component.rb +136 -0
  11. data/app/components/better_ui/application/sidebar/component.html.erb +190 -0
  12. data/app/components/better_ui/application/sidebar/component.rb +129 -0
  13. data/app/components/better_ui/general/alert/component.html.erb +32 -0
  14. data/app/components/better_ui/general/alert/component.rb +242 -0
  15. data/app/components/better_ui/general/avatar/component.html.erb +20 -0
  16. data/app/components/better_ui/general/avatar/component.rb +301 -0
  17. data/app/components/better_ui/general/badge/component.html.erb +23 -0
  18. data/app/components/better_ui/general/badge/component.rb +248 -0
  19. data/app/components/better_ui/general/breadcrumb/component.html.erb +15 -0
  20. data/app/components/better_ui/general/breadcrumb/component.rb +187 -0
  21. data/app/components/better_ui/general/button/component.html.erb +34 -0
  22. data/app/components/better_ui/general/button/component.rb +214 -0
  23. data/app/components/better_ui/general/divider/component.html.erb +10 -0
  24. data/app/components/better_ui/general/divider/component.rb +226 -0
  25. data/app/components/better_ui/general/dropdown/component.html.erb +14 -0
  26. data/app/components/better_ui/general/dropdown/component.rb +219 -0
  27. data/app/components/better_ui/general/dropdown/divider_component.html.erb +1 -0
  28. data/app/components/better_ui/general/dropdown/divider_component.rb +41 -0
  29. data/app/components/better_ui/general/dropdown/item_component.html.erb +6 -0
  30. data/app/components/better_ui/general/dropdown/item_component.rb +118 -0
  31. data/app/components/better_ui/general/field/component.html.erb +27 -0
  32. data/app/components/better_ui/general/field/component.rb +37 -0
  33. data/app/components/better_ui/general/heading/component.html.erb +22 -0
  34. data/app/components/better_ui/general/heading/component.rb +257 -0
  35. data/app/components/better_ui/general/icon/component.html.erb +7 -0
  36. data/app/components/better_ui/general/icon/component.rb +239 -0
  37. data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
  38. data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
  39. data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
  40. data/app/components/better_ui/general/input/datetime/component.rb +223 -0
  41. data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
  42. data/app/components/better_ui/general/input/radio/component.rb +230 -0
  43. data/app/components/better_ui/general/input/select/component.html.erb +16 -0
  44. data/app/components/better_ui/general/input/select/component.rb +184 -0
  45. data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
  46. data/app/components/better_ui/general/input/select/select_component.rb +37 -0
  47. data/app/components/better_ui/general/input/text/component.html.erb +5 -0
  48. data/app/components/better_ui/general/input/text/component.rb +171 -0
  49. data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
  50. data/app/components/better_ui/general/input/textarea/component.rb +166 -0
  51. data/app/components/better_ui/general/link/component.html.erb +18 -0
  52. data/app/components/better_ui/general/link/component.rb +258 -0
  53. data/app/components/better_ui/general/modal/component.html.erb +42 -0
  54. data/app/components/better_ui/general/modal/component.rb +165 -0
  55. data/app/components/better_ui/general/pagination/component.html.erb +85 -0
  56. data/app/components/better_ui/general/pagination/component.rb +216 -0
  57. data/app/components/better_ui/general/panel/component.html.erb +28 -0
  58. data/app/components/better_ui/general/panel/component.rb +249 -0
  59. data/app/components/better_ui/general/progress/component.html.erb +11 -0
  60. data/app/components/better_ui/general/progress/component.rb +160 -0
  61. data/app/components/better_ui/general/spinner/component.html.erb +35 -0
  62. data/app/components/better_ui/general/spinner/component.rb +93 -0
  63. data/app/components/better_ui/general/table/component.html.erb +5 -0
  64. data/app/components/better_ui/general/table/component.rb +217 -0
  65. data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
  66. data/app/components/better_ui/general/table/tbody_component.rb +30 -0
  67. data/app/components/better_ui/general/table/td_component.html.erb +3 -0
  68. data/app/components/better_ui/general/table/td_component.rb +44 -0
  69. data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
  70. data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
  71. data/app/components/better_ui/general/table/th_component.html.erb +6 -0
  72. data/app/components/better_ui/general/table/th_component.rb +51 -0
  73. data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
  74. data/app/components/better_ui/general/table/thead_component.rb +28 -0
  75. data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
  76. data/app/components/better_ui/general/table/tr_component.rb +30 -0
  77. data/app/components/better_ui/general/tabs/component.html.erb +3 -0
  78. data/app/components/better_ui/general/tabs/component.rb +102 -0
  79. data/app/components/better_ui/general/tabs/panel_component.html.erb +3 -0
  80. data/app/components/better_ui/general/tabs/panel_component.rb +37 -0
  81. data/app/components/better_ui/general/tabs/tab_component.html.erb +13 -0
  82. data/app/components/better_ui/general/tabs/tab_component.rb +111 -0
  83. data/app/components/better_ui/general/tag/component.html.erb +3 -0
  84. data/app/components/better_ui/general/tag/component.rb +104 -0
  85. data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
  86. data/app/components/better_ui/general/tooltip/component.rb +239 -0
  87. data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
  88. data/app/helpers/better_ui/application/components/card.rb +11 -0
  89. data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
  90. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
  91. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
  92. data/app/helpers/better_ui/application_helper.rb +51 -179
  93. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
  94. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
  95. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
  96. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
  97. data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
  98. data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
  99. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
  100. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +32 -0
  101. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +79 -0
  102. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +62 -0
  103. data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
  104. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
  105. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
  106. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
  107. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
  108. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
  109. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
  110. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
  111. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
  112. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
  113. data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
  114. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +95 -0
  115. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +82 -0
  116. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
  117. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
  118. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
  119. data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
  120. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
  121. data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
  122. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
  123. data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
  124. data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
  125. data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
  126. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +62 -0
  127. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +55 -0
  128. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +62 -0
  129. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
  130. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
  131. data/app/views/layouts/better_ui/application.html.erb +6 -124
  132. data/config/initializers/lookbook.rb +23 -0
  133. data/config/routes.rb +0 -8
  134. data/lib/better_ui/engine.rb +5 -19
  135. data/lib/better_ui/railtie.rb +20 -0
  136. data/lib/better_ui/version.rb +1 -1
  137. data/lib/better_ui.rb +4 -20
  138. metadata +155 -28
  139. data/app/controllers/better_ui/docs_controller.rb +0 -41
  140. data/app/views/better_ui/docs/component.html.erb +0 -365
  141. data/app/views/better_ui/docs/index.html.erb +0 -100
  142. data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -0,0 +1,10 @@
1
+ <%# Template per il divider %>
2
+ <% if show_label? %>
3
+ <%= tag.div(**divider_attributes) do %>
4
+ <div class="<%= label_line_classes %>"></div>
5
+ <span class="<%= label_classes %>"><%= @label %></span>
6
+ <div class="<%= label_line_classes %>"></div>
7
+ <% end %>
8
+ <% else %>
9
+ <%= tag.div(**divider_attributes) %>
10
+ <% end %>
@@ -0,0 +1,226 @@
1
+ module BetterUi
2
+ module General
3
+ module Divider
4
+ class Component < ViewComponent::Base
5
+ attr_reader :theme, :orientation, :style, :size, :label, :height, :classes
6
+
7
+ # Classi base sempre presenti
8
+ DIVIDER_BASE_CLASSES = "my-4"
9
+
10
+ # Temi con classi Tailwind dirette - LOGICA CORRETTA
11
+ DIVIDER_THEME_CLASSES = {
12
+ default: "border-white", # Bordo bianco (per sfondi scuri)
13
+ white: "border-gray-300", # Bordo grigio (per sfondi chiari)
14
+ red: "border-red-500",
15
+ rose: "border-rose-500",
16
+ orange: "border-orange-500",
17
+ green: "border-green-500",
18
+ blue: "border-blue-500",
19
+ yellow: "border-yellow-500",
20
+ violet: "border-violet-500"
21
+ }
22
+
23
+ # Orientamento con classi Tailwind dirette
24
+ DIVIDER_ORIENTATION_CLASSES = {
25
+ horizontal: "w-full border-t",
26
+ vertical: "h-full border-l"
27
+ }
28
+
29
+ # Stili di linea con classi Tailwind dirette
30
+ DIVIDER_STYLE_CLASSES = {
31
+ solid: "border-solid",
32
+ dashed: "border-dashed",
33
+ dotted: "border-dotted",
34
+ double: "border-double"
35
+ }
36
+
37
+ # Dimensioni con classi Tailwind dirette
38
+ DIVIDER_SIZE_CLASSES = {
39
+ thin: {
40
+ horizontal: "border-t",
41
+ vertical: "border-l"
42
+ },
43
+ medium: {
44
+ horizontal: "border-t-2",
45
+ vertical: "border-l-2"
46
+ },
47
+ thick: {
48
+ horizontal: "border-t-4",
49
+ vertical: "border-l-4"
50
+ }
51
+ }
52
+
53
+ # Classi per label con classi Tailwind dirette - LOGICA CORRETTA
54
+ DIVIDER_LABEL_THEME_CLASSES = {
55
+ default: "text-white bg-transparent px-3", # Testo bianco trasparente (per sfondi scuri)
56
+ white: "text-gray-900 bg-white px-3", # Testo nero su bianco (per sfondi chiari)
57
+ red: "text-red-500 bg-white px-3",
58
+ rose: "text-rose-500 bg-white px-3",
59
+ orange: "text-orange-500 bg-white px-3",
60
+ green: "text-green-500 bg-white px-3",
61
+ blue: "text-blue-500 bg-white px-3",
62
+ yellow: "text-yellow-600 bg-white px-3",
63
+ violet: "text-violet-500 bg-white px-3"
64
+ }
65
+
66
+ # @param theme [Symbol] tema del divider (:default, :white, etc.)
67
+ # @param orientation [Symbol] orientamento del divider (:horizontal, :vertical)
68
+ # @param style [Symbol] stile della linea (:solid, :dashed, :dotted, :double)
69
+ # @param size [Symbol] dimensione della linea (:thin, :medium, :thick)
70
+ # @param label [String] testo opzionale da mostrare al centro del divider
71
+ # @param height [String] altezza per divider verticale (es. "100px", "100%")
72
+ # @param classes [String] classi CSS aggiuntive
73
+ # @param html_options [Hash] opzioni HTML per il container
74
+ def initialize(
75
+ theme: :white,
76
+ orientation: :horizontal,
77
+ style: :solid,
78
+ size: :medium,
79
+ label: nil,
80
+ height: nil,
81
+ classes: nil,
82
+ **html_options
83
+ )
84
+ @theme = theme.to_sym
85
+ @orientation = orientation.to_sym
86
+ @style = style.to_sym
87
+ @size = size.to_sym
88
+ @label = label
89
+ @height = height
90
+ @classes = classes
91
+ @html_options = html_options
92
+
93
+ validate_params
94
+ end
95
+
96
+ # Combina tutte le classi per il container
97
+ def combined_classes
98
+ base_classes = []
99
+
100
+ if @label.present? && @orientation == :horizontal
101
+ # Per divider con label orizzontale: flex layout
102
+ base_classes = [
103
+ "flex items-center text-center",
104
+ get_theme_class,
105
+ get_style_class
106
+ ]
107
+ else
108
+ # Per divider normale
109
+ base_classes = [
110
+ DIVIDER_BASE_CLASSES,
111
+ get_orientation_class,
112
+ get_theme_class,
113
+ get_style_class,
114
+ get_size_class
115
+ ]
116
+ end
117
+
118
+ [ *base_classes, @classes, @html_options[:class] ].compact.join(" ")
119
+ end
120
+
121
+ # Classi per il label
122
+ def label_classes
123
+ return "" unless @label.present?
124
+
125
+ [
126
+ "relative z-10 text-sm font-medium",
127
+ get_label_theme_class
128
+ ].compact.join(" ")
129
+ end
130
+
131
+ # Classi per le linee before/after quando c'è un label
132
+ def label_line_classes
133
+ return "" unless @label.present? && @orientation == :horizontal
134
+
135
+ [
136
+ "flex-1 h-px",
137
+ get_theme_class,
138
+ get_style_class
139
+ ].compact.join(" ")
140
+ end
141
+
142
+ # Genera gli attributi di stile inline necessari
143
+ def inline_styles
144
+ return nil unless @orientation == :vertical && @height.present?
145
+ "height: #{@height};"
146
+ end
147
+
148
+ # Restituisce gli attributi per il divider
149
+ def divider_attributes
150
+ attrs = {
151
+ class: combined_classes
152
+ }
153
+
154
+ # Aggiungi stile inline se presente
155
+ attrs[:style] = inline_styles if inline_styles.present?
156
+
157
+ # Aggiungi altri attributi HTML se presenti
158
+ @html_options.except(:class).each do |key, value|
159
+ attrs[key] = value
160
+ end
161
+
162
+ attrs
163
+ end
164
+
165
+ # Determina se mostrare il label
166
+ def show_label?
167
+ @label.present? && @orientation == :horizontal
168
+ end
169
+
170
+ private
171
+
172
+ def get_theme_class
173
+ DIVIDER_THEME_CLASSES[@theme] || DIVIDER_THEME_CLASSES[:white]
174
+ end
175
+
176
+ def get_orientation_class
177
+ DIVIDER_ORIENTATION_CLASSES[@orientation] || DIVIDER_ORIENTATION_CLASSES[:horizontal]
178
+ end
179
+
180
+ def get_style_class
181
+ DIVIDER_STYLE_CLASSES[@style] || DIVIDER_STYLE_CLASSES[:solid]
182
+ end
183
+
184
+ def get_size_class
185
+ size_map = DIVIDER_SIZE_CLASSES[@size] || DIVIDER_SIZE_CLASSES[:medium]
186
+ size_map[@orientation] || size_map[:horizontal]
187
+ end
188
+
189
+ def get_label_theme_class
190
+ DIVIDER_LABEL_THEME_CLASSES[@theme] || DIVIDER_LABEL_THEME_CLASSES[:white]
191
+ end
192
+
193
+ def validate_params
194
+ validate_theme
195
+ validate_orientation
196
+ validate_style
197
+ validate_size
198
+ end
199
+
200
+ def validate_theme
201
+ unless DIVIDER_THEME_CLASSES.keys.include?(@theme)
202
+ raise ArgumentError, "Il tema deve essere uno tra: #{DIVIDER_THEME_CLASSES.keys.join(', ')}"
203
+ end
204
+ end
205
+
206
+ def validate_orientation
207
+ unless DIVIDER_ORIENTATION_CLASSES.keys.include?(@orientation)
208
+ raise ArgumentError, "L'orientamento deve essere uno tra: #{DIVIDER_ORIENTATION_CLASSES.keys.join(', ')}"
209
+ end
210
+ end
211
+
212
+ def validate_style
213
+ unless DIVIDER_STYLE_CLASSES.keys.include?(@style)
214
+ raise ArgumentError, "Lo stile deve essere uno tra: #{DIVIDER_STYLE_CLASSES.keys.join(', ')}"
215
+ end
216
+ end
217
+
218
+ def validate_size
219
+ unless DIVIDER_SIZE_CLASSES.keys.include?(@size)
220
+ raise ArgumentError, "La dimensione deve essere una tra: #{DIVIDER_SIZE_CLASSES.keys.join(', ')}"
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,14 @@
1
+ <div <%= tag.attributes(container_attributes) %>>
2
+ <button <%= tag.attributes(trigger_attributes) %>>
3
+ <%= @trigger %>
4
+ <svg class="ml-2 -mr-1 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
5
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
6
+ </svg>
7
+ </button>
8
+
9
+ <div <%= tag.attributes(menu_attributes) %>>
10
+ <div class="py-1" role="none">
11
+ <%= content %>
12
+ </div>
13
+ </div>
14
+ </div>
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Dropdown
6
+ class Component < ViewComponent::Base
7
+ attr_reader :trigger, :position, :theme, :size, :rounded, :animation, :classes, :html_options
8
+
9
+ # Classi base per il contenitore dropdown
10
+ DROPDOWN_CONTAINER_CLASSES = "relative inline-block"
11
+
12
+ # Classi base per il pulsante trigger
13
+ DROPDOWN_TRIGGER_BASE_CLASSES = "inline-flex items-center justify-center border font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors"
14
+
15
+ # Classi base per il menu dropdown
16
+ DROPDOWN_MENU_BASE_CLASSES = "absolute z-50 mt-2 origin-top-right bg-white border border-gray-200 shadow-lg focus:outline-none"
17
+
18
+ # Temi per il trigger del dropdown con classi Tailwind dirette
19
+ DROPDOWN_TRIGGER_THEME = {
20
+ default: "bg-white border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-blue-500",
21
+ white: "bg-white border-gray-300 text-gray-900 hover:bg-gray-50 focus:ring-gray-500",
22
+ red: "bg-red-600 border-red-600 text-white hover:bg-red-700 focus:ring-red-500",
23
+ rose: "bg-rose-600 border-rose-600 text-white hover:bg-rose-700 focus:ring-rose-500",
24
+ orange: "bg-orange-600 border-orange-600 text-white hover:bg-orange-700 focus:ring-orange-500",
25
+ green: "bg-green-600 border-green-600 text-white hover:bg-green-700 focus:ring-green-500",
26
+ blue: "bg-blue-600 border-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
27
+ yellow: "bg-yellow-500 border-yellow-500 text-white hover:bg-yellow-600 focus:ring-yellow-500",
28
+ violet: "bg-violet-600 border-violet-600 text-white hover:bg-violet-700 focus:ring-violet-500"
29
+ }.freeze
30
+
31
+ # Dimensioni del trigger con classi Tailwind dirette
32
+ DROPDOWN_TRIGGER_SIZE = {
33
+ small: "px-3 py-1.5 text-sm",
34
+ medium: "px-4 py-2 text-sm",
35
+ large: "px-6 py-3 text-base"
36
+ }.freeze
37
+
38
+ # Border radius con classi Tailwind dirette
39
+ DROPDOWN_ROUNDED = {
40
+ none: "rounded-none",
41
+ small: "rounded-md",
42
+ medium: "rounded-lg",
43
+ large: "rounded-xl",
44
+ full: "rounded-full"
45
+ }.freeze
46
+
47
+ # Posizioni del menu dropdown
48
+ DROPDOWN_POSITION = {
49
+ bottom: "top-full left-0",
50
+ top: "bottom-full left-0",
51
+ left: "top-0 right-full mr-2",
52
+ right: "top-0 left-full ml-2"
53
+ }.freeze
54
+
55
+ # Animazioni del dropdown
56
+ DROPDOWN_ANIMATION = {
57
+ fade: "transition-opacity duration-150",
58
+ slide: "transition-all duration-150 transform",
59
+ none: ""
60
+ }.freeze
61
+
62
+ def initialize(
63
+ trigger:,
64
+ position: :bottom,
65
+ theme: :default,
66
+ size: :medium,
67
+ rounded: :medium,
68
+ animation: :fade,
69
+ classes: nil,
70
+ **html_options
71
+ )
72
+ @trigger = trigger
73
+ @position = position.to_sym
74
+ @theme = theme.to_sym
75
+ @size = size.to_sym
76
+ @rounded = rounded.to_sym
77
+ @animation = animation.to_sym
78
+ @classes = classes
79
+ @html_options = html_options
80
+
81
+ validate_params
82
+ end
83
+
84
+ # Combina tutte le classi per il contenitore
85
+ def container_classes
86
+ [
87
+ DROPDOWN_CONTAINER_CLASSES,
88
+ @classes
89
+ ].compact.join(" ")
90
+ end
91
+
92
+ # Combina tutte le classi per il trigger
93
+ def trigger_classes
94
+ [
95
+ DROPDOWN_TRIGGER_BASE_CLASSES,
96
+ get_trigger_theme_classes,
97
+ get_trigger_size_classes,
98
+ get_trigger_rounded_classes
99
+ ].compact.join(" ")
100
+ end
101
+
102
+ # Combina tutte le classi per il menu
103
+ def menu_classes
104
+ [
105
+ DROPDOWN_MENU_BASE_CLASSES,
106
+ get_position_classes,
107
+ get_animation_classes,
108
+ get_menu_rounded_classes
109
+ ].compact.join(" ")
110
+ end
111
+
112
+ # Restituisce gli attributi per il contenitore
113
+ def container_attributes
114
+ attrs = {
115
+ class: container_classes,
116
+ "data-dropdown": true
117
+ }
118
+
119
+ @html_options.except(:class).each do |key, value|
120
+ attrs[key] = value
121
+ end
122
+
123
+ attrs
124
+ end
125
+
126
+ # Restituisce gli attributi per il trigger
127
+ def trigger_attributes
128
+ {
129
+ type: "button",
130
+ class: trigger_classes,
131
+ "data-dropdown-trigger": true,
132
+ "aria-expanded": "false",
133
+ "aria-haspopup": "true"
134
+ }
135
+ end
136
+
137
+ # Restituisce gli attributi per il menu
138
+ def menu_attributes
139
+ {
140
+ class: menu_classes,
141
+ "data-dropdown-menu": true,
142
+ role: "menu",
143
+ "aria-orientation": "vertical",
144
+ style: "display: none;"
145
+ }
146
+ end
147
+
148
+ # Verifica se rendere il componente
149
+ def render?
150
+ @trigger.present?
151
+ end
152
+
153
+ private
154
+
155
+ def get_trigger_theme_classes
156
+ DROPDOWN_TRIGGER_THEME[@theme] || DROPDOWN_TRIGGER_THEME[:default]
157
+ end
158
+
159
+ def get_trigger_size_classes
160
+ DROPDOWN_TRIGGER_SIZE[@size] || DROPDOWN_TRIGGER_SIZE[:medium]
161
+ end
162
+
163
+ def get_trigger_rounded_classes
164
+ DROPDOWN_ROUNDED[@rounded] || DROPDOWN_ROUNDED[:medium]
165
+ end
166
+
167
+ def get_menu_rounded_classes
168
+ DROPDOWN_ROUNDED[@rounded] || DROPDOWN_ROUNDED[:medium]
169
+ end
170
+
171
+ def get_position_classes
172
+ DROPDOWN_POSITION[@position] || DROPDOWN_POSITION[:bottom]
173
+ end
174
+
175
+ def get_animation_classes
176
+ DROPDOWN_ANIMATION[@animation] || DROPDOWN_ANIMATION[:fade]
177
+ end
178
+
179
+ def validate_params
180
+ validate_theme
181
+ validate_size
182
+ validate_rounded
183
+ validate_position
184
+ validate_animation
185
+ end
186
+
187
+ def validate_theme
188
+ unless DROPDOWN_TRIGGER_THEME.keys.include?(@theme)
189
+ raise ArgumentError, "Il tema deve essere uno tra: #{DROPDOWN_TRIGGER_THEME.keys.join(', ')}"
190
+ end
191
+ end
192
+
193
+ def validate_size
194
+ unless DROPDOWN_TRIGGER_SIZE.keys.include?(@size)
195
+ raise ArgumentError, "La dimensione deve essere una tra: #{DROPDOWN_TRIGGER_SIZE.keys.join(', ')}"
196
+ end
197
+ end
198
+
199
+ def validate_rounded
200
+ unless DROPDOWN_ROUNDED.keys.include?(@rounded)
201
+ raise ArgumentError, "Il border radius deve essere uno tra: #{DROPDOWN_ROUNDED.keys.join(', ')}"
202
+ end
203
+ end
204
+
205
+ def validate_position
206
+ unless DROPDOWN_POSITION.keys.include?(@position)
207
+ raise ArgumentError, "La posizione deve essere una tra: #{DROPDOWN_POSITION.keys.join(', ')}"
208
+ end
209
+ end
210
+
211
+ def validate_animation
212
+ unless DROPDOWN_ANIMATION.keys.include?(@animation)
213
+ raise ArgumentError, "L'animazione deve essere una tra: #{DROPDOWN_ANIMATION.keys.join(', ')}"
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1 @@
1
+ <div <%= tag.attributes(divider_attributes) %>></div>
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Dropdown
6
+ class DividerComponent < ViewComponent::Base
7
+ attr_reader :classes, :html_options
8
+
9
+ # Classi base per il divisore del dropdown
10
+ DROPDOWN_DIVIDER_CLASSES = "border-t border-gray-100 my-1"
11
+
12
+ def initialize(classes: nil, **html_options)
13
+ @classes = classes
14
+ @html_options = html_options
15
+ end
16
+
17
+ # Combina tutte le classi per il divisore
18
+ def divider_classes
19
+ [
20
+ DROPDOWN_DIVIDER_CLASSES,
21
+ @classes
22
+ ].compact.join(" ")
23
+ end
24
+
25
+ # Restituisce gli attributi per il divisore
26
+ def divider_attributes
27
+ attrs = {
28
+ class: divider_classes,
29
+ role: "separator"
30
+ }
31
+
32
+ @html_options.except(:class).each do |key, value|
33
+ attrs[key] = value
34
+ end
35
+
36
+ attrs
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ <<%= tag_name %> <%= tag.attributes(item_attributes) %>>
2
+ <% if @icon.present? %>
3
+ <span class="mr-3 flex-shrink-0"><%= bui_icon(@icon, classes: "h-5 w-5") %></span>
4
+ <% end %>
5
+ <%= @text %>
6
+ </<%= tag_name %>>
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Dropdown
6
+ class ItemComponent < ViewComponent::Base
7
+ include BetterUi::General::Components::Icon::IconHelper
8
+
9
+ attr_reader :text, :icon, :href, :theme, :disabled, :active, :classes, :html_options
10
+
11
+ # Classi base per l'elemento del dropdown
12
+ DROPDOWN_ITEM_BASE_CLASSES = "flex items-center w-full px-4 py-2 text-sm transition-colors"
13
+
14
+ # Temi per gli elementi del dropdown con classi Tailwind dirette
15
+ DROPDOWN_ITEM_THEME = {
16
+ default: "text-gray-700 hover:bg-gray-100 hover:text-gray-900",
17
+ white: "text-gray-900 hover:bg-gray-50",
18
+ red: "text-red-700 hover:bg-red-50 hover:text-red-900",
19
+ rose: "text-rose-700 hover:bg-rose-50 hover:text-rose-900",
20
+ orange: "text-orange-700 hover:bg-orange-50 hover:text-orange-900",
21
+ green: "text-green-700 hover:bg-green-50 hover:text-green-900",
22
+ blue: "text-blue-700 hover:bg-blue-50 hover:text-blue-900",
23
+ yellow: "text-yellow-700 hover:bg-yellow-50 hover:text-yellow-900",
24
+ violet: "text-violet-700 hover:bg-violet-50 hover:text-violet-900"
25
+ }.freeze
26
+
27
+ # Stati per gli elementi del dropdown
28
+ DROPDOWN_ITEM_STATE_DISABLED = "opacity-50 cursor-not-allowed pointer-events-none"
29
+ DROPDOWN_ITEM_STATE_ACTIVE = "bg-gray-100 text-gray-900"
30
+
31
+ def initialize(
32
+ text:,
33
+ icon: nil,
34
+ href: nil,
35
+ theme: :default,
36
+ disabled: false,
37
+ active: false,
38
+ classes: nil,
39
+ **html_options
40
+ )
41
+ @text = text
42
+ @icon = icon
43
+ @href = href
44
+ @theme = theme.to_sym
45
+ @disabled = disabled
46
+ @active = active
47
+ @classes = classes
48
+ @html_options = html_options
49
+
50
+ validate_params
51
+ end
52
+
53
+ # Combina tutte le classi per l'elemento
54
+ def item_classes
55
+ classes = [
56
+ DROPDOWN_ITEM_BASE_CLASSES,
57
+ get_theme_classes,
58
+ @classes
59
+ ]
60
+
61
+ classes << DROPDOWN_ITEM_STATE_DISABLED if @disabled
62
+ classes << DROPDOWN_ITEM_STATE_ACTIVE if @active
63
+
64
+ classes.compact.join(" ")
65
+ end
66
+
67
+ # Restituisce gli attributi per l'elemento
68
+ def item_attributes
69
+ attrs = {
70
+ class: item_classes,
71
+ role: "menuitem"
72
+ }
73
+
74
+ if @href.present? && !@disabled
75
+ attrs[:href] = @href
76
+ end
77
+
78
+ if @disabled
79
+ attrs["aria-disabled"] = "true"
80
+ attrs[:tabindex] = "-1"
81
+ end
82
+
83
+ @html_options.except(:class).each do |key, value|
84
+ attrs[key] = value
85
+ end
86
+
87
+ attrs
88
+ end
89
+
90
+ # Determina se usare un link o un button
91
+ def tag_name
92
+ @href.present? && !@disabled ? :a : :button
93
+ end
94
+
95
+ # Verifica se rendere il componente
96
+ def render?
97
+ @text.present?
98
+ end
99
+
100
+ private
101
+
102
+ def get_theme_classes
103
+ DROPDOWN_ITEM_THEME[@theme] || DROPDOWN_ITEM_THEME[:default]
104
+ end
105
+
106
+ def validate_params
107
+ validate_theme
108
+ end
109
+
110
+ def validate_theme
111
+ unless DROPDOWN_ITEM_THEME.keys.include?(@theme)
112
+ raise ArgumentError, "Il tema deve essere uno tra: #{DROPDOWN_ITEM_THEME.keys.join(', ')}"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,27 @@
1
+ <%# Form field component template %>
2
+ <div class="<%= BASE_CLASSES %>">
3
+ <% if label.present? %>
4
+ <label for="<%= id %>" class="<%= LABEL_CLASSES %>">
5
+ <%= label %>
6
+ <% if required %>
7
+ <span class="<%= REQUIRED_CLASSES %>">*</span>
8
+ <% end %>
9
+ </label>
10
+ <% end %>
11
+
12
+ <div class="mt-1">
13
+ <%= content %>
14
+ </div>
15
+
16
+ <% if error.present? %>
17
+ <div class="<%= ERROR_CLASSES %>">
18
+ <%= error %>
19
+ </div>
20
+ <% end %>
21
+
22
+ <% if help_text.present? %>
23
+ <div class="<%= HELP_TEXT_CLASSES %>">
24
+ <%= help_text %>
25
+ </div>
26
+ <% end %>
27
+ </div>