better_ui 0.2.0 → 0.6.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/better_ui/application/main/component.html.erb +1 -1
  3. data/app/components/better_ui/application/sidebar/component.html.erb +77 -18
  4. data/app/components/better_ui/application/sidebar/component.rb +63 -5
  5. data/app/components/better_ui/general/accordion/component.html.erb +5 -0
  6. data/app/components/better_ui/general/accordion/component.rb +92 -0
  7. data/app/components/better_ui/general/accordion/item_component.html.erb +12 -0
  8. data/app/components/better_ui/general/accordion/item_component.rb +176 -0
  9. data/app/components/better_ui/general/button/component.html.erb +8 -8
  10. data/app/components/better_ui/general/button/component.rb +11 -11
  11. data/app/components/better_ui/general/dropdown/component.html.erb +21 -7
  12. data/app/components/better_ui/general/dropdown/component.rb +27 -54
  13. data/app/components/better_ui/general/dropdown/item_component.rb +2 -1
  14. data/app/components/better_ui/general/field/component.html.erb +3 -3
  15. data/app/components/better_ui/general/field/component.rb +3 -3
  16. data/app/components/better_ui/general/grid/cell_component.html.erb +3 -0
  17. data/app/components/better_ui/general/grid/cell_component.rb +390 -0
  18. data/app/components/better_ui/general/grid/component.html.erb +3 -0
  19. data/app/components/better_ui/general/grid/component.rb +301 -0
  20. data/app/components/better_ui/general/heading/component.html.erb +1 -1
  21. data/app/components/better_ui/general/icon/component.rb +2 -1
  22. data/app/components/better_ui/general/input/checkbox/component.rb +10 -10
  23. data/app/components/better_ui/general/input/pin/component.html.erb +1 -0
  24. data/app/components/better_ui/general/input/pin/component.rb +201 -0
  25. data/app/components/better_ui/general/input/radio/component.rb +10 -10
  26. data/app/components/better_ui/general/input/rating/component.html.erb +4 -0
  27. data/app/components/better_ui/general/input/rating/component.rb +272 -0
  28. data/app/components/better_ui/general/input/select/component.html.erb +76 -14
  29. data/app/components/better_ui/general/input/select/component.rb +166 -101
  30. data/app/components/better_ui/general/input/toggle/component.html.erb +5 -0
  31. data/app/components/better_ui/general/input/toggle/component.rb +242 -0
  32. data/app/components/better_ui/general/link/component.rb +1 -1
  33. data/app/components/better_ui/general/modal/component.html.erb +5 -42
  34. data/app/components/better_ui/general/modal/component.rb +22 -140
  35. data/app/components/better_ui/general/modal/modal_component.html.erb +52 -0
  36. data/app/components/better_ui/general/modal/modal_component.rb +160 -0
  37. data/app/components/better_ui/general/tabs/component.html.erb +10 -2
  38. data/app/components/better_ui/general/tabs/component.rb +26 -8
  39. data/app/components/better_ui/general/tabs/panel_component.rb +1 -1
  40. data/app/components/better_ui/general/tabs/tab_component.rb +1 -1
  41. data/app/components/better_ui/general/text/component.html.erb +1 -0
  42. data/app/components/better_ui/general/text/component.rb +194 -0
  43. data/app/helpers/better_ui/application_helper.rb +11 -4
  44. data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +73 -0
  45. data/app/helpers/better_ui/general/components/button/button_helper.rb +6 -6
  46. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +9 -0
  47. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +13 -7
  48. data/app/helpers/better_ui/general/components/field/field_helper.rb +4 -4
  49. data/app/helpers/better_ui/general/components/grid/grid_helper.rb +145 -0
  50. data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +76 -0
  51. data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +70 -0
  52. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +47 -31
  53. data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +77 -0
  54. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +34 -44
  55. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +59 -26
  56. data/app/helpers/better_ui/general/components/text/text_helper.rb +83 -0
  57. data/lib/better_ui/version.rb +1 -1
  58. data/lib/better_ui.rb +1 -0
  59. metadata +26 -2
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Grid
6
+ class Component < ViewComponent::Base
7
+ attr_reader :cols, :rows, :gap, :flow, :align_items, :justify_items, :classes, :id, :html_options
8
+
9
+ # Classi base grid
10
+ GRID_BASE_CLASSES = "grid"
11
+
12
+ # Valori supportati per validazione
13
+ VALID_GRID_COLS = (1..12).to_a + [:auto, :none].freeze
14
+ VALID_GRID_ROWS = (1..6).to_a + [:auto, :none].freeze
15
+ VALID_BREAKPOINTS = [:sm, :md, :lg, :xl].freeze
16
+
17
+ # Gap (spaziatura)
18
+ GRID_GAP = {
19
+ none: 'gap-0', small: 'gap-2', medium: 'gap-4', large: 'gap-6'
20
+ }.freeze
21
+
22
+ # Flow (direzione)
23
+ GRID_FLOW = {
24
+ row: 'grid-flow-row', col: 'grid-flow-col',
25
+ row_dense: 'grid-flow-row-dense', col_dense: 'grid-flow-col-dense'
26
+ }.freeze
27
+
28
+ # Align items
29
+ GRID_ALIGN_ITEMS = {
30
+ start: 'items-start', center: 'items-center',
31
+ end: 'items-end', stretch: 'items-stretch'
32
+ }.freeze
33
+
34
+ # Justify items
35
+ GRID_JUSTIFY_ITEMS = {
36
+ start: 'justify-items-start', center: 'justify-items-center',
37
+ end: 'justify-items-end', stretch: 'justify-items-stretch'
38
+ }.freeze
39
+
40
+ def initialize(
41
+ cols: 1,
42
+ rows: nil,
43
+ gap: :medium,
44
+ flow: :row,
45
+ align_items: nil,
46
+ justify_items: nil,
47
+ classes: '',
48
+ id: nil,
49
+ **html_options
50
+ )
51
+ @cols = normalize_grid_cols_with_defaults(cols)
52
+ @rows = normalize_grid_rows_with_defaults(rows) if rows
53
+ @gap = normalize_grid_gap_with_defaults(gap)
54
+ @flow = flow.to_sym
55
+ @align_items = align_items&.to_sym
56
+ @justify_items = justify_items&.to_sym
57
+ @classes = classes
58
+ @id = id
59
+ @html_options = html_options
60
+
61
+ validate_grid_params
62
+ end
63
+
64
+ def combined_classes
65
+ [
66
+ GRID_BASE_CLASSES,
67
+ generate_cols_classes,
68
+ generate_rows_classes,
69
+ generate_gap_classes,
70
+ GRID_FLOW[@flow],
71
+ @align_items ? GRID_ALIGN_ITEMS[@align_items] : nil,
72
+ @justify_items ? GRID_JUSTIFY_ITEMS[@justify_items] : nil,
73
+ @classes,
74
+ @html_options[:class]
75
+ ].compact.join(" ")
76
+ end
77
+
78
+ def grid_attributes
79
+ attrs = {
80
+ class: combined_classes,
81
+ id: @id
82
+ }
83
+
84
+ @html_options.except(:class).each do |key, value|
85
+ attrs[key] = value
86
+ end
87
+
88
+ attrs
89
+ end
90
+
91
+ private
92
+
93
+ # Genera classi CSS dinamicamente invece di usare costanti massive
94
+ def generate_grid_cols_class(cols, breakpoint = nil)
95
+ prefix = breakpoint ? "#{breakpoint}:" : ""
96
+ "#{prefix}grid-cols-#{cols}"
97
+ end
98
+
99
+ def generate_grid_rows_class(rows, breakpoint = nil)
100
+ prefix = breakpoint ? "#{breakpoint}:" : ""
101
+ "#{prefix}grid-rows-#{rows}"
102
+ end
103
+
104
+ def generate_grid_gap_class(gap, breakpoint = nil)
105
+ prefix = breakpoint ? "#{breakpoint}:" : ""
106
+ gap_class = GRID_GAP[gap.to_sym]
107
+ return nil unless gap_class
108
+
109
+ if breakpoint
110
+ "#{breakpoint}:#{gap_class.sub(/^gap-/, 'gap-')}"
111
+ else
112
+ gap_class
113
+ end
114
+ end
115
+
116
+ # Normalizza cols con default intelligenti per tutti i breakpoint
117
+ def normalize_grid_cols_with_defaults(cols)
118
+ # Se è un valore semplice, lo usiamo per tutti i breakpoint
119
+ return cols unless cols.is_a?(Hash)
120
+
121
+ # Se è un hash, riempiamo i breakpoint mancanti con fallback intelligente
122
+ normalized = {}
123
+ last_value = 1 # Default base
124
+
125
+ # Base (senza breakpoint)
126
+ normalized[:base] = 1
127
+
128
+ # Processa i breakpoint in ordine
129
+ VALID_BREAKPOINTS.each do |breakpoint|
130
+ if cols.key?(breakpoint)
131
+ last_value = cols[breakpoint]
132
+ end
133
+ normalized[breakpoint] = last_value
134
+ end
135
+
136
+ normalized
137
+ end
138
+
139
+ # Normalizza rows con default intelligenti (solo se specificato)
140
+ def normalize_grid_rows_with_defaults(rows)
141
+ return rows unless rows.is_a?(Hash)
142
+
143
+ normalized = {}
144
+ last_value = nil
145
+
146
+ VALID_BREAKPOINTS.each do |breakpoint|
147
+ if rows.key?(breakpoint)
148
+ last_value = rows[breakpoint]
149
+ end
150
+ normalized[breakpoint] = last_value if last_value
151
+ end
152
+
153
+ normalized
154
+ end
155
+
156
+ # Normalizza gap con default intelligenti
157
+ def normalize_grid_gap_with_defaults(gap)
158
+ return gap unless gap.is_a?(Hash)
159
+
160
+ normalized = {}
161
+ last_value = :medium # Default base
162
+
163
+ VALID_BREAKPOINTS.each do |breakpoint|
164
+ if gap.key?(breakpoint)
165
+ last_value = gap[breakpoint]
166
+ end
167
+ normalized[breakpoint] = last_value
168
+ end
169
+
170
+ normalized
171
+ end
172
+
173
+ def generate_cols_classes
174
+ return nil unless @cols
175
+
176
+ if @cols.is_a?(Hash)
177
+ classes = []
178
+
179
+ # Base class (senza breakpoint)
180
+ if @cols[:base]
181
+ classes << generate_grid_cols_class(@cols[:base])
182
+ end
183
+
184
+ # Responsive classes
185
+ VALID_BREAKPOINTS.each do |breakpoint|
186
+ if @cols[breakpoint]
187
+ classes << generate_grid_cols_class(@cols[breakpoint], breakpoint)
188
+ end
189
+ end
190
+
191
+ classes.join(" ")
192
+ else
193
+ generate_grid_cols_class(@cols)
194
+ end
195
+ end
196
+
197
+ def generate_rows_classes
198
+ return nil unless @rows
199
+
200
+ if @rows.is_a?(Hash)
201
+ @rows.map do |breakpoint, rows_value|
202
+ generate_grid_rows_class(rows_value, breakpoint)
203
+ end.compact.join(" ")
204
+ else
205
+ generate_grid_rows_class(@rows)
206
+ end
207
+ end
208
+
209
+ def generate_gap_classes
210
+ if @gap.is_a?(Hash)
211
+ @gap.map do |breakpoint, gap_value|
212
+ generate_grid_gap_class(gap_value, breakpoint)
213
+ end.compact.join(" ")
214
+ else
215
+ GRID_GAP[@gap.to_sym]
216
+ end
217
+ end
218
+
219
+ def validate_grid_params
220
+ validate_grid_cols
221
+ validate_grid_rows if @rows
222
+ validate_grid_gap
223
+ validate_grid_flow
224
+ validate_grid_align_items if @align_items
225
+ validate_grid_justify_items if @justify_items
226
+ end
227
+
228
+ def validate_grid_cols
229
+ if @cols.is_a?(Hash)
230
+ @cols.each do |breakpoint, cols_value|
231
+ next if breakpoint == :base # Skip validation for base
232
+
233
+ unless VALID_BREAKPOINTS.include?(breakpoint)
234
+ raise ArgumentError, "Breakpoint #{breakpoint} non supportato per cols"
235
+ end
236
+ unless VALID_GRID_COLS.include?(cols_value)
237
+ raise ArgumentError, "Valore cols #{cols_value} non supportato per breakpoint #{breakpoint}"
238
+ end
239
+ end
240
+ else
241
+ unless VALID_GRID_COLS.include?(@cols)
242
+ raise ArgumentError, "cols deve essere uno tra: #{VALID_GRID_COLS.join(', ')}"
243
+ end
244
+ end
245
+ end
246
+
247
+ def validate_grid_rows
248
+ if @rows.is_a?(Hash)
249
+ @rows.each do |breakpoint, rows_value|
250
+ unless VALID_BREAKPOINTS.include?(breakpoint)
251
+ raise ArgumentError, "Breakpoint #{breakpoint} non supportato per rows"
252
+ end
253
+ unless VALID_GRID_ROWS.include?(rows_value)
254
+ raise ArgumentError, "Valore rows #{rows_value} non supportato per breakpoint #{breakpoint}"
255
+ end
256
+ end
257
+ else
258
+ unless VALID_GRID_ROWS.include?(@rows)
259
+ raise ArgumentError, "rows deve essere uno tra: #{VALID_GRID_ROWS.join(', ')}"
260
+ end
261
+ end
262
+ end
263
+
264
+ def validate_grid_gap
265
+ if @gap.is_a?(Hash)
266
+ @gap.each do |breakpoint, gap_value|
267
+ unless VALID_BREAKPOINTS.include?(breakpoint)
268
+ raise ArgumentError, "Breakpoint #{breakpoint} non supportato per gap"
269
+ end
270
+ unless GRID_GAP.keys.include?(gap_value.to_sym)
271
+ raise ArgumentError, "Valore gap #{gap_value} non supportato per breakpoint #{breakpoint}"
272
+ end
273
+ end
274
+ else
275
+ unless GRID_GAP.keys.include?(@gap.to_sym)
276
+ raise ArgumentError, "gap deve essere uno tra: #{GRID_GAP.keys.join(', ')}"
277
+ end
278
+ end
279
+ end
280
+
281
+ def validate_grid_flow
282
+ unless GRID_FLOW.keys.include?(@flow)
283
+ raise ArgumentError, "flow deve essere uno tra: #{GRID_FLOW.keys.join(', ')}"
284
+ end
285
+ end
286
+
287
+ def validate_grid_align_items
288
+ unless GRID_ALIGN_ITEMS.keys.include?(@align_items)
289
+ raise ArgumentError, "align_items deve essere uno tra: #{GRID_ALIGN_ITEMS.keys.join(', ')}"
290
+ end
291
+ end
292
+
293
+ def validate_grid_justify_items
294
+ unless GRID_JUSTIFY_ITEMS.keys.include?(@justify_items)
295
+ raise ArgumentError, "justify_items deve essere uno tra: #{GRID_JUSTIFY_ITEMS.keys.join(', ')}"
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
301
+ end
@@ -3,7 +3,7 @@
3
3
  <%= tag.public_send(heading_tag, **heading_attributes) do %>
4
4
  <% if show_icon? %>
5
5
  <span class="<%= icon_classes %>">
6
- <%= render BetterUi::General::IconComponent.new(name: @icon) %>
6
+ <%= render BetterUi::General::Icon::Component.new(name: @icon) %>
7
7
  </span>
8
8
  <% end %>
9
9
 
@@ -9,7 +9,8 @@ module BetterUi
9
9
  ICON_SIZE_CLASSES = {
10
10
  small: "bui-icon--small w-4 h-4 text-sm",
11
11
  medium: "bui-icon--medium w-5 h-5 text-base",
12
- large: "bui-icon--large w-6 h-6 text-lg"
12
+ large: "bui-icon--large w-6 h-6 text-lg",
13
+ xlarge: "bui-icon--large w-7 h-7 text-xl"
13
14
  }.freeze
14
15
 
15
16
  # Temi dell'icona con colori coerenti
@@ -7,7 +7,7 @@ module BetterUi
7
7
  class Component < ViewComponent::Base
8
8
  # Costanti con classi Tailwind dirette
9
9
  CHECKBOX_THEME = {
10
- default: 'border-gray-300 text-blue-600 focus:border-blue-500 focus:ring-blue-500 checked:bg-blue-600 checked:border-blue-600',
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
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
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
13
  rose: 'border-gray-300 text-rose-600 focus:border-rose-500 focus:ring-rose-500 checked:bg-rose-600 checked:border-rose-600',
@@ -19,9 +19,9 @@ module BetterUi
19
19
  }.freeze
20
20
 
21
21
  CHECKBOX_SIZE = {
22
- small: 'h-4 w-4',
23
- medium: 'h-5 w-5',
24
- large: 'h-6 w-6'
22
+ small: 'h-2.5 w-2.5',
23
+ medium: 'h-3 w-3',
24
+ large: 'h-4 w-4'
25
25
  }.freeze
26
26
 
27
27
  CHECKBOX_ROUNDED = {
@@ -35,15 +35,15 @@ module BetterUi
35
35
  CHECKBOX_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
36
 
37
37
  CHECKBOX_LABEL_GAP = {
38
- small: 'gap-2',
39
- medium: 'gap-2.5',
40
- large: 'gap-3'
38
+ small: 'gap-1.5',
39
+ medium: 'gap-2',
40
+ large: 'gap-2.5'
41
41
  }.freeze
42
42
 
43
43
  CHECKBOX_LABEL_TEXT = {
44
- small: 'text-sm',
45
- medium: 'text-base',
46
- large: 'text-lg'
44
+ small: 'text-xs',
45
+ medium: 'text-sm',
46
+ large: 'text-base'
47
47
  }.freeze
48
48
 
49
49
  attr_reader :name, :value, :checked, :required, :disabled, :indeterminate,
@@ -0,0 +1 @@
1
+ <%= render_pin_container %>
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Input
6
+ module Pin
7
+ class Component < ViewComponent::Base
8
+ # Costanti con classi Tailwind dirette
9
+ PIN_THEME = {
10
+ default: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
11
+ white: 'border-gray-300 bg-white focus:border-gray-900 focus:ring-gray-900',
12
+ red: 'border-red-300 focus:border-red-500 focus:ring-red-500',
13
+ rose: 'border-rose-300 focus:border-rose-500 focus:ring-rose-500',
14
+ orange: 'border-orange-300 focus:border-orange-500 focus:ring-orange-500',
15
+ green: 'border-green-300 focus:border-green-500 focus:ring-green-500',
16
+ blue: 'border-blue-300 focus:border-blue-500 focus:ring-blue-500',
17
+ yellow: 'border-yellow-300 focus:border-yellow-500 focus:ring-yellow-500',
18
+ violet: 'border-violet-300 focus:border-violet-500 focus:ring-violet-500'
19
+ }.freeze
20
+
21
+ PIN_SIZE = {
22
+ small: 'w-8 h-8 text-sm',
23
+ medium: 'w-12 h-12 text-base',
24
+ large: 'w-16 h-16 text-lg'
25
+ }.freeze
26
+
27
+ PIN_GAP = {
28
+ small: 'gap-2',
29
+ medium: 'gap-3',
30
+ large: 'gap-4'
31
+ }.freeze
32
+
33
+ PIN_BASE_CLASSES = 'rounded-md border text-center font-mono focus:outline-none focus:ring-2 focus:ring-offset-1 transition-colors'.freeze
34
+
35
+ attr_reader :name, :value, :length, :placeholder, :required, :disabled,
36
+ :theme, :size, :form, :classes, :options
37
+
38
+ # @param name [String] Nome del campo pin (obbligatorio)
39
+ # @param value [String] Valore del pin preimpostato
40
+ # @param length [Integer] Numero di campi pin (4-8, default: 6)
41
+ # @param placeholder [String] Placeholder per campi vuoti (default: '•')
42
+ # @param required [Boolean] Se il campo è obbligatorio
43
+ # @param disabled [Boolean] Se il campo è disabilitato
44
+ # @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
45
+ # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
46
+ # @param form [ActionView::Helpers::FormBuilder, nil] Form builder Rails opzionale
47
+ # @param classes [String] Classi CSS aggiuntive
48
+ # @param options [Hash] Opzioni aggiuntive per attributi HTML
49
+ def initialize(name:, value: '', length: 6, placeholder: '•', required: false, disabled: false,
50
+ theme: :default, size: :medium, form: nil, classes: '', **options)
51
+ @name = name
52
+ @value = value.to_s
53
+ @length = length.to_i
54
+ @placeholder = placeholder
55
+ @required = required
56
+ @disabled = disabled
57
+ @theme = theme.to_sym
58
+ @size = size.to_sym
59
+ @form = form
60
+ @classes = classes
61
+ @options = options
62
+
63
+ validate_params
64
+ end
65
+
66
+ private
67
+
68
+ def validate_params
69
+ validate_theme
70
+ validate_size
71
+ validate_length
72
+ validate_name
73
+ end
74
+
75
+ def validate_theme
76
+ return if PIN_THEME.key?(@theme)
77
+
78
+ raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{PIN_THEME.keys.join(', ')}"
79
+ end
80
+
81
+ def validate_size
82
+ return if PIN_SIZE.key?(@size)
83
+
84
+ raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{PIN_SIZE.keys.join(', ')}"
85
+ end
86
+
87
+ def validate_length
88
+ return if @length >= 4 && @length <= 8
89
+
90
+ raise ArgumentError, "Length must be between 4 and 8, got: #{@length}"
91
+ end
92
+
93
+ def validate_name
94
+ return if @name.present?
95
+
96
+ raise ArgumentError, "Name is required for pin input components"
97
+ end
98
+
99
+ def container_classes
100
+ base_classes = ['flex', 'items-center']
101
+ base_classes << PIN_GAP[@size]
102
+ base_classes << @classes if @classes.present?
103
+ base_classes.join(' ')
104
+ end
105
+
106
+ def input_classes
107
+ [
108
+ PIN_BASE_CLASSES,
109
+ PIN_SIZE[@size],
110
+ PIN_THEME[@theme],
111
+ (@disabled ? 'opacity-50 cursor-not-allowed' : '')
112
+ ].compact.join(' ')
113
+ end
114
+
115
+ def controller_attributes
116
+ {
117
+ data: {
118
+ controller: 'bui-pin',
119
+ 'bui-pin-length-value': @length,
120
+ 'bui-pin-name-value': @name,
121
+ 'bui-pin-placeholder-value': @placeholder
122
+ }
123
+ }
124
+ end
125
+
126
+ def input_attributes(index)
127
+ attrs = {
128
+ type: 'text',
129
+ class: input_classes,
130
+ maxlength: '1',
131
+ autocomplete: 'off',
132
+ inputmode: 'numeric',
133
+ pattern: '[0-9]*',
134
+ placeholder: @placeholder,
135
+ disabled: @disabled,
136
+ required: @required && index == 0, # Solo il primo campo è required per validazione
137
+ data: {
138
+ 'bui-pin-target': 'input',
139
+ action: [
140
+ 'input->bui-pin#inputChange',
141
+ 'keydown->bui-pin#inputKeydown',
142
+ 'paste->bui-pin#inputPaste'
143
+ ].join(' ')
144
+ }
145
+ }
146
+
147
+ # Imposta valore se presente
148
+ if @value.present? && @value[index]
149
+ attrs[:value] = @value[index]
150
+ end
151
+
152
+ attrs.merge(@options.except(:id))
153
+ end
154
+
155
+ def hidden_input_attributes
156
+ attrs = {
157
+ type: 'hidden',
158
+ name: input_name,
159
+ value: @value,
160
+ data: { 'bui-pin-target': 'hiddenInput' }
161
+ }
162
+
163
+ # Aggiungi ID se specificato
164
+ if @options[:id]
165
+ attrs[:id] = @options[:id]
166
+ end
167
+
168
+ attrs
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 render_pin_inputs
180
+ @length.times.map do |index|
181
+ tag(:input, input_attributes(index))
182
+ end
183
+ end
184
+
185
+ def render_hidden_input
186
+ tag(:input, hidden_input_attributes)
187
+ end
188
+
189
+ def render_pin_container
190
+ content_tag(:div, class: container_classes, **controller_attributes) do
191
+ safe_join([
192
+ render_hidden_input,
193
+ *render_pin_inputs
194
+ ])
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -7,7 +7,7 @@ module BetterUi
7
7
  class Component < ViewComponent::Base
8
8
  # Costanti con classi Tailwind dirette
9
9
  RADIO_THEME = {
10
- default: 'border-gray-300 text-blue-600 focus:border-blue-500 focus:ring-blue-500 checked:bg-blue-600 checked:border-blue-600',
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
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
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
13
  rose: 'border-gray-300 text-rose-600 focus:border-rose-500 focus:ring-rose-500 checked:bg-rose-600 checked:border-rose-600',
@@ -19,9 +19,9 @@ module BetterUi
19
19
  }.freeze
20
20
 
21
21
  RADIO_SIZE = {
22
- small: 'h-4 w-4',
23
- medium: 'h-5 w-5',
24
- large: 'h-6 w-6'
22
+ small: 'h-2.5 w-2.5',
23
+ medium: 'h-3 w-3',
24
+ large: 'h-4 w-4'
25
25
  }.freeze
26
26
 
27
27
  RADIO_ROUNDED = {
@@ -35,15 +35,15 @@ module BetterUi
35
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
36
 
37
37
  RADIO_LABEL_GAP = {
38
- small: 'gap-2',
39
- medium: 'gap-2.5',
40
- large: 'gap-3'
38
+ small: 'gap-1.5',
39
+ medium: 'gap-2',
40
+ large: 'gap-2.5'
41
41
  }.freeze
42
42
 
43
43
  RADIO_LABEL_TEXT = {
44
- small: 'text-sm',
45
- medium: 'text-base',
46
- large: 'text-lg'
44
+ small: 'text-xs',
45
+ medium: 'text-sm',
46
+ large: 'text-base'
47
47
  }.freeze
48
48
 
49
49
  attr_reader :name, :value, :checked, :required, :disabled,
@@ -0,0 +1,4 @@
1
+ <div class="<%= container_classes %>">
2
+ <%= render_stars_container %>
3
+ <%= render_value_display %>
4
+ </div>