better_ui 0.6.0 → 0.7.1

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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +257 -212
  3. data/Rakefile +11 -2
  4. data/app/components/better_ui/action_messages_component/action_messages_component.html.erb +48 -0
  5. data/app/components/better_ui/action_messages_component.rb +544 -0
  6. data/app/components/better_ui/application_component.rb +66 -0
  7. data/app/components/better_ui/button_component/button_component.html.erb +31 -0
  8. data/app/components/better_ui/button_component.rb +307 -0
  9. data/app/components/better_ui/card_component/card_component.html.erb +17 -0
  10. data/app/components/better_ui/card_component.rb +460 -0
  11. data/app/components/better_ui/drawer/header_component/header_component.html.erb +24 -0
  12. data/app/components/better_ui/drawer/header_component.rb +238 -0
  13. data/app/components/better_ui/drawer/layout_component/layout_component.html.erb +44 -0
  14. data/app/components/better_ui/drawer/layout_component.rb +270 -0
  15. data/app/components/better_ui/drawer/nav_group_component/nav_group_component.html.erb +10 -0
  16. data/app/components/better_ui/drawer/nav_group_component.rb +155 -0
  17. data/app/components/better_ui/drawer/nav_item_component/nav_item_component.html.erb +13 -0
  18. data/app/components/better_ui/drawer/nav_item_component.rb +225 -0
  19. data/app/components/better_ui/drawer/sidebar_component/sidebar_component.html.erb +17 -0
  20. data/app/components/better_ui/drawer/sidebar_component.rb +263 -0
  21. data/app/components/better_ui/forms/base_component.rb +450 -0
  22. data/app/components/better_ui/forms/checkbox_component/checkbox_component.html.erb +28 -0
  23. data/app/components/better_ui/forms/checkbox_component.rb +419 -0
  24. data/app/components/better_ui/forms/checkbox_group_component/checkbox_group_component.html.erb +40 -0
  25. data/app/components/better_ui/forms/checkbox_group_component.rb +363 -0
  26. data/app/components/better_ui/forms/number_input_component/number_input_component.html.erb +40 -0
  27. data/app/components/better_ui/forms/number_input_component.rb +320 -0
  28. data/app/components/better_ui/forms/password_input_component/password_input_component.html.erb +71 -0
  29. data/app/components/better_ui/forms/password_input_component.rb +206 -0
  30. data/app/components/better_ui/forms/text_input_component/text_input_component.html.erb +40 -0
  31. data/app/components/better_ui/forms/text_input_component.rb +258 -0
  32. data/app/components/better_ui/forms/textarea_component/textarea_component.html.erb +40 -0
  33. data/app/components/better_ui/forms/textarea_component.rb +329 -0
  34. data/app/form_builders/better_ui/ui_form_builder.rb +467 -0
  35. data/app/helpers/better_ui/application_helper.rb +325 -58
  36. data/app/views/layouts/better_ui/application.html.erb +1 -1
  37. data/config/routes.rb +1 -0
  38. data/lib/better_ui/engine.rb +34 -5
  39. data/lib/better_ui/version.rb +1 -1
  40. data/lib/better_ui.rb +32 -5
  41. data/lib/generators/better_ui/install/USAGE +44 -0
  42. data/lib/generators/better_ui/install/install_generator.rb +87 -0
  43. data/lib/generators/better_ui/install/templates/better_ui_theme.css.tt +280 -0
  44. data/lib/tasks/better_ui_tasks.rake +39 -4
  45. metadata +55 -203
  46. data/app/components/better_ui/application/card/component.html.erb +0 -20
  47. data/app/components/better_ui/application/card/component.rb +0 -214
  48. data/app/components/better_ui/application/main/component.html.erb +0 -9
  49. data/app/components/better_ui/application/main/component.rb +0 -123
  50. data/app/components/better_ui/application/navbar/component.html.erb +0 -92
  51. data/app/components/better_ui/application/navbar/component.rb +0 -136
  52. data/app/components/better_ui/application/sidebar/component.html.erb +0 -249
  53. data/app/components/better_ui/application/sidebar/component.rb +0 -187
  54. data/app/components/better_ui/general/accordion/component.html.erb +0 -5
  55. data/app/components/better_ui/general/accordion/component.rb +0 -92
  56. data/app/components/better_ui/general/accordion/item_component.html.erb +0 -12
  57. data/app/components/better_ui/general/accordion/item_component.rb +0 -176
  58. data/app/components/better_ui/general/alert/component.html.erb +0 -32
  59. data/app/components/better_ui/general/alert/component.rb +0 -242
  60. data/app/components/better_ui/general/avatar/component.html.erb +0 -20
  61. data/app/components/better_ui/general/avatar/component.rb +0 -301
  62. data/app/components/better_ui/general/badge/component.html.erb +0 -23
  63. data/app/components/better_ui/general/badge/component.rb +0 -248
  64. data/app/components/better_ui/general/breadcrumb/component.html.erb +0 -15
  65. data/app/components/better_ui/general/breadcrumb/component.rb +0 -187
  66. data/app/components/better_ui/general/button/component.html.erb +0 -34
  67. data/app/components/better_ui/general/button/component.rb +0 -214
  68. data/app/components/better_ui/general/divider/component.html.erb +0 -10
  69. data/app/components/better_ui/general/divider/component.rb +0 -226
  70. data/app/components/better_ui/general/dropdown/component.html.erb +0 -28
  71. data/app/components/better_ui/general/dropdown/component.rb +0 -192
  72. data/app/components/better_ui/general/dropdown/divider_component.html.erb +0 -1
  73. data/app/components/better_ui/general/dropdown/divider_component.rb +0 -41
  74. data/app/components/better_ui/general/dropdown/item_component.html.erb +0 -6
  75. data/app/components/better_ui/general/dropdown/item_component.rb +0 -119
  76. data/app/components/better_ui/general/field/component.html.erb +0 -27
  77. data/app/components/better_ui/general/field/component.rb +0 -37
  78. data/app/components/better_ui/general/grid/cell_component.html.erb +0 -3
  79. data/app/components/better_ui/general/grid/cell_component.rb +0 -390
  80. data/app/components/better_ui/general/grid/component.html.erb +0 -3
  81. data/app/components/better_ui/general/grid/component.rb +0 -301
  82. data/app/components/better_ui/general/heading/component.html.erb +0 -22
  83. data/app/components/better_ui/general/heading/component.rb +0 -257
  84. data/app/components/better_ui/general/icon/component.html.erb +0 -7
  85. data/app/components/better_ui/general/icon/component.rb +0 -240
  86. data/app/components/better_ui/general/input/checkbox/component.html.erb +0 -5
  87. data/app/components/better_ui/general/input/checkbox/component.rb +0 -238
  88. data/app/components/better_ui/general/input/datetime/component.html.erb +0 -5
  89. data/app/components/better_ui/general/input/datetime/component.rb +0 -223
  90. data/app/components/better_ui/general/input/pin/component.html.erb +0 -1
  91. data/app/components/better_ui/general/input/pin/component.rb +0 -201
  92. data/app/components/better_ui/general/input/radio/component.html.erb +0 -5
  93. data/app/components/better_ui/general/input/radio/component.rb +0 -230
  94. data/app/components/better_ui/general/input/rating/component.html.erb +0 -4
  95. data/app/components/better_ui/general/input/rating/component.rb +0 -272
  96. data/app/components/better_ui/general/input/select/component.html.erb +0 -78
  97. data/app/components/better_ui/general/input/select/component.rb +0 -249
  98. data/app/components/better_ui/general/input/select/select_component.html.erb +0 -5
  99. data/app/components/better_ui/general/input/select/select_component.rb +0 -37
  100. data/app/components/better_ui/general/input/text/component.html.erb +0 -5
  101. data/app/components/better_ui/general/input/text/component.rb +0 -171
  102. data/app/components/better_ui/general/input/textarea/component.html.erb +0 -5
  103. data/app/components/better_ui/general/input/textarea/component.rb +0 -166
  104. data/app/components/better_ui/general/input/toggle/component.html.erb +0 -5
  105. data/app/components/better_ui/general/input/toggle/component.rb +0 -242
  106. data/app/components/better_ui/general/link/component.html.erb +0 -18
  107. data/app/components/better_ui/general/link/component.rb +0 -258
  108. data/app/components/better_ui/general/modal/component.html.erb +0 -5
  109. data/app/components/better_ui/general/modal/component.rb +0 -47
  110. data/app/components/better_ui/general/modal/modal_component.html.erb +0 -52
  111. data/app/components/better_ui/general/modal/modal_component.rb +0 -160
  112. data/app/components/better_ui/general/pagination/component.html.erb +0 -85
  113. data/app/components/better_ui/general/pagination/component.rb +0 -216
  114. data/app/components/better_ui/general/panel/component.html.erb +0 -28
  115. data/app/components/better_ui/general/panel/component.rb +0 -249
  116. data/app/components/better_ui/general/progress/component.html.erb +0 -11
  117. data/app/components/better_ui/general/progress/component.rb +0 -160
  118. data/app/components/better_ui/general/spinner/component.html.erb +0 -35
  119. data/app/components/better_ui/general/spinner/component.rb +0 -93
  120. data/app/components/better_ui/general/table/component.html.erb +0 -5
  121. data/app/components/better_ui/general/table/component.rb +0 -217
  122. data/app/components/better_ui/general/table/tbody_component.html.erb +0 -3
  123. data/app/components/better_ui/general/table/tbody_component.rb +0 -30
  124. data/app/components/better_ui/general/table/td_component.html.erb +0 -3
  125. data/app/components/better_ui/general/table/td_component.rb +0 -44
  126. data/app/components/better_ui/general/table/tfoot_component.html.erb +0 -3
  127. data/app/components/better_ui/general/table/tfoot_component.rb +0 -28
  128. data/app/components/better_ui/general/table/th_component.html.erb +0 -6
  129. data/app/components/better_ui/general/table/th_component.rb +0 -51
  130. data/app/components/better_ui/general/table/thead_component.html.erb +0 -3
  131. data/app/components/better_ui/general/table/thead_component.rb +0 -28
  132. data/app/components/better_ui/general/table/tr_component.html.erb +0 -3
  133. data/app/components/better_ui/general/table/tr_component.rb +0 -30
  134. data/app/components/better_ui/general/tabs/component.html.erb +0 -11
  135. data/app/components/better_ui/general/tabs/component.rb +0 -120
  136. data/app/components/better_ui/general/tabs/panel_component.html.erb +0 -3
  137. data/app/components/better_ui/general/tabs/panel_component.rb +0 -37
  138. data/app/components/better_ui/general/tabs/tab_component.html.erb +0 -13
  139. data/app/components/better_ui/general/tabs/tab_component.rb +0 -111
  140. data/app/components/better_ui/general/tag/component.html.erb +0 -3
  141. data/app/components/better_ui/general/tag/component.rb +0 -104
  142. data/app/components/better_ui/general/text/component.html.erb +0 -1
  143. data/app/components/better_ui/general/text/component.rb +0 -194
  144. data/app/components/better_ui/general/tooltip/component.html.erb +0 -7
  145. data/app/components/better_ui/general/tooltip/component.rb +0 -239
  146. data/app/helpers/better_ui/application/components/card/card_helper.rb +0 -96
  147. data/app/helpers/better_ui/application/components/card.rb +0 -11
  148. data/app/helpers/better_ui/application/components/main/main_helper.rb +0 -64
  149. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +0 -77
  150. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +0 -51
  151. data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +0 -73
  152. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +0 -57
  153. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +0 -29
  154. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +0 -53
  155. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +0 -37
  156. data/app/helpers/better_ui/general/components/button/button_helper.rb +0 -65
  157. data/app/helpers/better_ui/general/components/container/container_helper.rb +0 -60
  158. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +0 -63
  159. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +0 -32
  160. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +0 -88
  161. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +0 -68
  162. data/app/helpers/better_ui/general/components/field/field_helper.rb +0 -26
  163. data/app/helpers/better_ui/general/components/grid/grid_helper.rb +0 -145
  164. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +0 -72
  165. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +0 -16
  166. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +0 -81
  167. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +0 -91
  168. data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +0 -76
  169. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +0 -79
  170. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +0 -124
  171. data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +0 -70
  172. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +0 -86
  173. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +0 -138
  174. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +0 -73
  175. data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +0 -77
  176. data/app/helpers/better_ui/general/components/link/link_helper.rb +0 -89
  177. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +0 -85
  178. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +0 -82
  179. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +0 -83
  180. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +0 -53
  181. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +0 -19
  182. data/app/helpers/better_ui/general/components/table/table_helper.rb +0 -53
  183. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +0 -13
  184. data/app/helpers/better_ui/general/components/table/td_helper.rb +0 -19
  185. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +0 -13
  186. data/app/helpers/better_ui/general/components/table/th_helper.rb +0 -19
  187. data/app/helpers/better_ui/general/components/table/thead_helper.rb +0 -13
  188. data/app/helpers/better_ui/general/components/table/tr_helper.rb +0 -13
  189. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +0 -62
  190. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +0 -55
  191. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +0 -95
  192. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +0 -26
  193. data/app/helpers/better_ui/general/components/text/text_helper.rb +0 -83
  194. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +0 -60
  195. data/app/jobs/better_ui/application_job.rb +0 -4
  196. data/app/mailers/better_ui/application_mailer.rb +0 -6
  197. data/config/initializers/lookbook.rb +0 -23
  198. data/lib/better_ui/railtie.rb +0 -20
@@ -1,230 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BetterUi
4
- module General
5
- module Input
6
- module Radio
7
- class Component < ViewComponent::Base
8
- # Costanti con classi Tailwind dirette
9
- RADIO_THEME = {
10
- default: 'border-gray-300 text-gray-800 focus:border-gray-600 focus:ring-gray-600 checked:bg-gray-800 checked:border-gray-800',
11
- white: 'border-gray-300 text-gray-900 focus:border-gray-500 focus:ring-gray-500 checked:bg-white checked:border-gray-900 checked:text-gray-900',
12
- red: 'border-gray-300 text-red-600 focus:border-red-500 focus:ring-red-500 checked:bg-red-600 checked:border-red-600',
13
- rose: 'border-gray-300 text-rose-600 focus:border-rose-500 focus:ring-rose-500 checked:bg-rose-600 checked:border-rose-600',
14
- orange: 'border-gray-300 text-orange-600 focus:border-orange-500 focus:ring-orange-500 checked:bg-orange-600 checked:border-orange-600',
15
- green: 'border-gray-300 text-green-600 focus:border-green-500 focus:ring-green-500 checked:bg-green-600 checked:border-green-600',
16
- blue: 'border-gray-300 text-blue-600 focus:border-blue-500 focus:ring-blue-500 checked:bg-blue-600 checked:border-blue-600',
17
- yellow: 'border-gray-300 text-yellow-600 focus:border-yellow-500 focus:ring-yellow-500 checked:bg-yellow-600 checked:border-yellow-600',
18
- violet: 'border-gray-300 text-violet-600 focus:border-violet-500 focus:ring-violet-500 checked:bg-violet-600 checked:border-violet-600'
19
- }.freeze
20
-
21
- RADIO_SIZE = {
22
- small: 'h-2.5 w-2.5',
23
- medium: 'h-3 w-3',
24
- large: 'h-4 w-4'
25
- }.freeze
26
-
27
- RADIO_ROUNDED = {
28
- none: 'rounded-none',
29
- small: 'rounded-sm',
30
- medium: 'rounded',
31
- large: 'rounded-lg',
32
- full: 'rounded-full'
33
- }.freeze
34
-
35
- RADIO_BASE_CLASSES = 'appearance-none border-2 focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors duration-200 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50'.freeze
36
-
37
- RADIO_LABEL_GAP = {
38
- small: 'gap-1.5',
39
- medium: 'gap-2',
40
- large: 'gap-2.5'
41
- }.freeze
42
-
43
- RADIO_LABEL_TEXT = {
44
- small: 'text-xs',
45
- medium: 'text-sm',
46
- large: 'text-base'
47
- }.freeze
48
-
49
- attr_reader :name, :value, :checked, :required, :disabled,
50
- :label, :label_position, :theme, :size, :rounded, :classes, :form, :options
51
-
52
- # @param name [String] Nome del campo radio (obbligatorio)
53
- # @param value [String] Valore del radio button (obbligatorio)
54
- # @param checked [Boolean] Se il radio è selezionato
55
- # @param required [Boolean] Se il campo è obbligatorio
56
- # @param disabled [Boolean] Se il campo è disabilitato
57
- # @param label [String, nil] Testo della label associata al radio
58
- # @param label_position [Symbol] Posizione della label (:left, :right)
59
- # @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
60
- # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
61
- # @param rounded [Symbol] Border radius (:none, :small, :medium, :large, :full)
62
- # @param classes [String] Classi CSS aggiuntive
63
- # @param form [ActionView::Helpers::FormBuilder, nil] Form builder Rails opzionale
64
- # @param options [Hash] Opzioni aggiuntive per l'input (es. data attributes, aria attributes)
65
- def initialize(name:, value:, checked: false, required: false, disabled: false,
66
- label: nil, label_position: :right, theme: :default,
67
- size: :medium, rounded: :full, classes: '', form: nil, **options)
68
- @name = name
69
- @value = value
70
- @checked = checked
71
- @required = required
72
- @disabled = disabled
73
- @label = label
74
- @label_position = label_position.to_sym
75
- @theme = theme.to_sym
76
- @size = size.to_sym
77
- @rounded = rounded.to_sym
78
- @classes = classes
79
- @form = form
80
- @options = options
81
-
82
- validate_params
83
- end
84
-
85
- private
86
-
87
- def validate_params
88
- validate_theme
89
- validate_size
90
- validate_rounded
91
- validate_label_position
92
- end
93
-
94
- def validate_theme
95
- return if RADIO_THEME.key?(@theme)
96
-
97
- raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{RADIO_THEME.keys.join(', ')}"
98
- end
99
-
100
- def validate_size
101
- return if RADIO_SIZE.key?(@size)
102
-
103
- raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{RADIO_SIZE.keys.join(', ')}"
104
- end
105
-
106
- def validate_rounded
107
- return if RADIO_ROUNDED.key?(@rounded)
108
-
109
- raise ArgumentError, "Invalid rounded: #{@rounded}. Valid rounded options are: #{RADIO_ROUNDED.keys.join(', ')}"
110
- end
111
-
112
- def validate_label_position
113
- return if [:left, :right].include?(@label_position)
114
-
115
- raise ArgumentError, "Invalid label_position: #{@label_position}. Valid positions are: left, right"
116
- end
117
-
118
- def radio_classes
119
- [
120
- RADIO_BASE_CLASSES,
121
- RADIO_THEME[@theme],
122
- RADIO_SIZE[@size],
123
- RADIO_ROUNDED[@rounded],
124
- @classes
125
- ].compact.join(' ')
126
- end
127
-
128
- def input_attributes
129
- attrs = {
130
- type: 'radio',
131
- name: input_name,
132
- value: @value,
133
- class: radio_classes,
134
- checked: @checked,
135
- required: @required,
136
- disabled: @disabled,
137
- id: input_id
138
- }
139
-
140
- # Unisci le opzioni personalizzate
141
- attrs.merge(@options)
142
- end
143
-
144
- def input_name
145
- if @form
146
- @form.field_name(@name)
147
- else
148
- @name
149
- end
150
- end
151
-
152
- def input_id
153
- @options[:id] || "radio_#{@name}_#{@value}"
154
- end
155
-
156
- def label_classes
157
- [
158
- 'flex items-center cursor-pointer',
159
- @disabled ? 'opacity-50 cursor-not-allowed' : '',
160
- RADIO_LABEL_GAP[@size]
161
- ].compact.join(' ')
162
- end
163
-
164
- def label_text_classes
165
- RADIO_LABEL_TEXT[@size]
166
- end
167
-
168
- def input_tag
169
- if @form
170
- form_radio
171
- else
172
- manual_input
173
- end
174
- end
175
-
176
- def form_radio
177
- @form.radio_button(@name, @value, {
178
- class: radio_classes,
179
- id: input_id,
180
- checked: @checked,
181
- disabled: @disabled,
182
- required: @required,
183
- **@options
184
- })
185
- end
186
-
187
- def manual_input
188
- attrs = input_attributes.map do |key, value|
189
- if value == true
190
- key.to_s
191
- elsif value == false || value.nil?
192
- nil
193
- else
194
- "#{key}=\"#{value}\""
195
- end
196
- end.compact.join(' ')
197
-
198
- "<input #{attrs} />".html_safe
199
- end
200
-
201
- def render_radio_with_label
202
- if @label_position == :left
203
- label_left_content
204
- else
205
- label_right_content
206
- end
207
- end
208
-
209
- def label_left_content
210
- content_tag(:label, class: label_classes, for: input_id) do
211
- safe_join([
212
- content_tag(:span, @label, class: label_text_classes),
213
- input_tag
214
- ])
215
- end
216
- end
217
-
218
- def label_right_content
219
- content_tag(:label, class: label_classes, for: input_id) do
220
- safe_join([
221
- input_tag,
222
- content_tag(:span, @label, class: label_text_classes)
223
- ])
224
- end
225
- end
226
- end
227
- end
228
- end
229
- end
230
- end
@@ -1,4 +0,0 @@
1
- <div class="<%= container_classes %>">
2
- <%= render_stars_container %>
3
- <%= render_value_display %>
4
- </div>
@@ -1,272 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BetterUi
4
- module General
5
- module Input
6
- module Rating
7
- class Component < ViewComponent::Base
8
- # Costanti con classi Tailwind dirette
9
- RATING_THEME = {
10
- default: 'text-yellow-400',
11
- yellow: 'text-yellow-400',
12
- orange: 'text-orange-400',
13
- red: 'text-red-400',
14
- pink: 'text-pink-400',
15
- purple: 'text-purple-400',
16
- blue: 'text-blue-400',
17
- green: 'text-green-400',
18
- gray: 'text-gray-400'
19
- }.freeze
20
-
21
- RATING_SIZE = {
22
- small: 'text-sm',
23
- medium: 'text-xl',
24
- large: 'text-3xl'
25
- }.freeze
26
-
27
- RATING_EMPTY_COLOR = 'text-gray-300'.freeze
28
- RATING_HALF_COLOR = 'text-yellow-300'.freeze
29
-
30
- attr_reader :name, :value, :max_stars, :readonly, :half_stars,
31
- :theme, :size, :show_value, :form, :classes, :options
32
-
33
- # @param name [String] Nome del campo rating (obbligatorio se non readonly)
34
- # @param value [Float, Integer] Valore del rating attuale (0.0 - max_stars)
35
- # @param max_stars [Integer] Numero massimo di stelle (default: 5)
36
- # @param readonly [Boolean] Se il rating è in sola lettura
37
- # @param half_stars [Boolean] Se supportare mezze stelle
38
- # @param theme [Symbol] Tema del componente (:default, :yellow, :orange, :red, :pink, :purple, :blue, :green, :gray)
39
- # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
40
- # @param show_value [Boolean] Se mostrare il valore numerico accanto alle stelle
41
- # @param form [ActionView::Helpers::FormBuilder, nil] Form builder Rails opzionale
42
- # @param classes [String] Classi CSS aggiuntive
43
- # @param options [Hash] Opzioni aggiuntive per attributi HTML
44
- def initialize(name: nil, value: 0, max_stars: 5, readonly: false, half_stars: true,
45
- theme: :default, size: :medium, show_value: false, form: nil,
46
- classes: '', **options)
47
- @name = name
48
- @value = value.to_f
49
- @max_stars = max_stars.to_i
50
- @readonly = readonly
51
- @half_stars = half_stars
52
- @theme = theme.to_sym
53
- @size = size.to_sym
54
- @show_value = show_value
55
- @form = form
56
- @classes = classes
57
- @options = options
58
-
59
- validate_params
60
- end
61
-
62
- private
63
-
64
- def validate_params
65
- validate_theme
66
- validate_size
67
- validate_value
68
- validate_max_stars
69
- validate_name if interactive?
70
- end
71
-
72
- def validate_theme
73
- return if RATING_THEME.key?(@theme)
74
-
75
- raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{RATING_THEME.keys.join(', ')}"
76
- end
77
-
78
- def validate_size
79
- return if RATING_SIZE.key?(@size)
80
-
81
- raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{RATING_SIZE.keys.join(', ')}"
82
- end
83
-
84
- def validate_value
85
- return if @value >= 0 && @value <= @max_stars
86
-
87
- raise ArgumentError, "Value must be between 0 and #{@max_stars}, got: #{@value}"
88
- end
89
-
90
- def validate_max_stars
91
- return if @max_stars > 0
92
-
93
- raise ArgumentError, "Max stars must be greater than 0, got: #{@max_stars}"
94
- end
95
-
96
- def validate_name
97
- return if @name.present?
98
-
99
- raise ArgumentError, "Name is required for interactive rating components"
100
- end
101
-
102
- def interactive?
103
- !@readonly
104
- end
105
-
106
- def container_classes
107
- base_classes = ['inline-flex', 'items-center', 'gap-1']
108
- base_classes << @classes if @classes.present?
109
- base_classes.join(' ')
110
- end
111
-
112
- def rating_container_classes
113
- base_classes = ['flex', 'items-center']
114
- base_classes << (interactive? ? 'cursor-pointer' : 'cursor-default')
115
- base_classes.join(' ')
116
- end
117
-
118
- def star_classes(index)
119
- base_classes = [RATING_SIZE[@size], 'transition-colors', 'duration-150']
120
- base_classes << (interactive? ? 'hover:scale-110 transform transition-transform' : '')
121
- base_classes.compact.join(' ')
122
- end
123
-
124
- def controller_attributes
125
- return {} unless interactive?
126
-
127
- {
128
- data: {
129
- controller: 'bui-rating',
130
- 'bui-rating-rating-value': @value,
131
- 'bui-rating-max-value': @max_stars,
132
- 'bui-rating-readonly-value': @readonly,
133
- 'bui-rating-half-stars-value': @half_stars,
134
- 'bui-rating-name-value': @name,
135
- action: 'keydown->bui-rating#keydown'
136
- },
137
- tabindex: '0'
138
- }
139
- end
140
-
141
- def star_attributes(index)
142
- attrs = {
143
- data: {
144
- 'bui-rating-target': 'star',
145
- index: index
146
- }
147
- }
148
-
149
- if interactive?
150
- attrs[:data][:action] = [
151
- 'click->bui-rating#starClick',
152
- 'mouseover->bui-rating#starHover',
153
- 'mouseleave->bui-rating#starLeave'
154
- ].join(' ')
155
- end
156
-
157
- attrs
158
- end
159
-
160
- def hidden_input_attributes
161
- return {} unless interactive?
162
-
163
- {
164
- type: 'hidden',
165
- name: input_name,
166
- value: @value,
167
- data: { 'bui-rating-target': 'hiddenInput' }
168
- }
169
- end
170
-
171
- def input_name
172
- if @form
173
- @form.field_name(@name)
174
- else
175
- @name
176
- end
177
- end
178
-
179
- def input_id
180
- @options[:id] || "rating_#{@name}"
181
- end
182
-
183
- def display_value
184
- if @value % 1 == 0
185
- @value.to_i.to_s
186
- else
187
- @value.to_s
188
- end
189
- end
190
-
191
- def star_display_state(index)
192
- star_number = index + 1
193
-
194
- if @value >= star_number
195
- :full
196
- elsif @half_stars && @value >= star_number - 0.5
197
- :half
198
- else
199
- :empty
200
- end
201
- end
202
-
203
- def star_color(state)
204
- case state
205
- when :full
206
- RATING_THEME[@theme]
207
- when :half
208
- RATING_HALF_COLOR
209
- when :empty
210
- RATING_EMPTY_COLOR
211
- end
212
- end
213
-
214
- def star_symbol(state)
215
- case state
216
- when :full, :half
217
- '★'
218
- when :empty
219
- '☆'
220
- end
221
- end
222
-
223
- def render_star(index)
224
- state = star_display_state(index)
225
-
226
- content_tag(:span, star_attributes(index).merge(class: "#{star_classes(index)} #{star_color(state)}")) do
227
- if state == :half
228
- render_half_star
229
- else
230
- star_symbol(state)
231
- end
232
- end
233
- end
234
-
235
- def render_half_star
236
- content_tag(:span, class: 'relative inline-block') do
237
- safe_join([
238
- content_tag(:span, '☆', class: RATING_EMPTY_COLOR),
239
- content_tag(:span, class: 'absolute inset-0 overflow-hidden w-1/2') do
240
- content_tag(:span, '★', class: RATING_THEME[@theme])
241
- end
242
- ])
243
- end
244
- end
245
-
246
- def render_hidden_input
247
- return unless interactive?
248
-
249
- tag(:input, hidden_input_attributes)
250
- end
251
-
252
- def render_value_display
253
- return unless @show_value
254
-
255
- content_tag(:span, class: "ml-2 text-sm text-gray-600", data: { 'bui-rating-target': 'display' }) do
256
- "(#{display_value})"
257
- end
258
- end
259
-
260
- def render_stars_container
261
- content_tag(:div, class: rating_container_classes, **controller_attributes) do
262
- safe_join([
263
- render_hidden_input,
264
- *@max_stars.times.map { |i| render_star(i) }
265
- ].compact)
266
- end
267
- end
268
- end
269
- end
270
- end
271
- end
272
- end
@@ -1,78 +0,0 @@
1
- <%= tag.div(**container_attributes) do %>
2
- <!-- Hidden input for form compatibility -->
3
- <input
4
- type="hidden"
5
- name="<%= input_name %>"
6
- id="<%= input_id %>"
7
- value="<%= hidden_input_value %>"
8
- data-select-target="hiddenInput"
9
- <%= "required" if @required %>
10
- <%= "disabled" if @disabled %>
11
- />
12
-
13
- <!-- Trigger button -->
14
- <button
15
- type="button"
16
- class="<%= trigger_classes %>"
17
- data-select-target="trigger"
18
- data-action="click->select#toggle"
19
- <%= "disabled" if @disabled %>
20
- >
21
- <span data-select-text class="<%= trigger_text_classes %>">
22
- <%= trigger_text %>
23
- </span>
24
- <%= chevron_icon %>
25
- </button>
26
-
27
- <!-- Badge container for multi-select -->
28
- <% if @multiple %>
29
- <div
30
- class="<%= badge_container_classes %>"
31
- data-select-target="badgeContainer"
32
- style="<%= @selected.empty? ? 'display: none;' : '' %>"
33
- >
34
- <!-- Badges will be populated by Stimulus -->
35
- </div>
36
- <% end %>
37
-
38
- <!-- Dropdown panel -->
39
- <div class="<%= dropdown_classes %>" data-select-target="dropdown">
40
- <!-- Search input -->
41
- <% if @searchable %>
42
- <div class="p-1">
43
- <input
44
- type="text"
45
- placeholder="<%= @search_placeholder %>"
46
- class="<%= search_input_classes %>"
47
- data-select-target="search"
48
- data-action="input->select#search"
49
- />
50
- </div>
51
- <% end %>
52
-
53
- <!-- Options container -->
54
- <div class="<%= options_container_classes %>">
55
- <% @options.each do |option| %>
56
- <div
57
- class="<%= option_classes %> <%= 'bg-gray-100' if option_selected?(option) %>"
58
- data-select-target="option"
59
- data-value="<%= option[:value] %>"
60
- data-action="click->select#selectOption"
61
- <%= "data-disabled" if option[:disabled] %>
62
- >
63
- <span><%= option[:label] %></span>
64
- <% if option_selected?(option) %>
65
- <span class="checkmark text-gray-600">✓</span>
66
- <% end %>
67
- </div>
68
- <% end %>
69
- </div>
70
-
71
- <!-- Empty state -->
72
- <% if @options.empty? %>
73
- <div class="px-3 py-6 text-center text-gray-500 text-sm">
74
- Nessuna opzione disponibile
75
- </div>
76
- <% end %>
77
- </div>
78
- <% end %>