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
@@ -5,136 +5,188 @@ module BetterUi
5
5
  module Input
6
6
  module Select
7
7
  class Component < ViewComponent::Base
8
- attr_reader :name, :options, :selected, :required, :disabled, :multiple,
9
- :theme, :size, :rounded, :placeholder, :form, :options_html,
10
- :classes, :html_options
11
-
12
- # Classi base sempre presenti
13
- SELECT_BASE_CLASSES = "block w-full border shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-0"
14
-
15
- # Temi di select con classi Tailwind
8
+ # Costanti con classi Tailwind dirette
16
9
  SELECT_THEME = {
17
- default: "bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500",
18
- white: "bg-white border-gray-300 focus:border-blue-500 focus:ring-blue-500",
19
- red: "bg-white border-red-300 focus:border-red-500 focus:ring-red-500",
20
- rose: "bg-white border-rose-300 focus:border-rose-500 focus:ring-rose-500",
21
- orange: "bg-white border-orange-300 focus:border-orange-500 focus:ring-orange-500",
22
- green: "bg-white border-green-300 focus:border-green-500 focus:ring-green-500",
23
- blue: "bg-white border-blue-300 focus:border-blue-500 focus:ring-blue-500",
24
- yellow: "bg-white border-yellow-300 focus:border-yellow-500 focus:ring-yellow-500",
25
- violet: "bg-white border-violet-300 focus:border-violet-500 focus:ring-violet-500"
10
+ default: 'border-gray-300 text-gray-800 focus:border-gray-600 focus:ring-gray-600',
11
+ white: 'border-gray-300 text-gray-900 focus:border-gray-500 focus:ring-gray-500',
12
+ red: 'border-gray-300 text-red-600 focus:border-red-500 focus:ring-red-500',
13
+ rose: 'border-gray-300 text-rose-600 focus:border-rose-500 focus:ring-rose-500',
14
+ orange: 'border-gray-300 text-orange-600 focus:border-orange-500 focus:ring-orange-500',
15
+ green: 'border-gray-300 text-green-600 focus:border-green-500 focus:ring-green-500',
16
+ blue: 'border-gray-300 text-blue-600 focus:border-blue-500 focus:ring-blue-500',
17
+ yellow: 'border-gray-300 text-yellow-600 focus:border-yellow-500 focus:ring-yellow-500',
18
+ violet: 'border-gray-300 text-violet-600 focus:border-violet-500 focus:ring-violet-500'
26
19
  }.freeze
27
20
 
28
- # Dimensioni con classi Tailwind
29
21
  SELECT_SIZE = {
30
- small: "px-2 py-1.5 text-xs",
31
- medium: "px-3 py-2 text-sm",
32
- large: "px-4 py-3 text-base"
33
- }.freeze
34
-
35
- # Border radius con classi Tailwind
36
- SELECT_ROUNDED = {
37
- none: "rounded-none",
38
- small: "rounded-sm",
39
- medium: "rounded-md",
40
- large: "rounded-lg",
41
- full: "rounded-full"
22
+ small: 'px-2 py-1.5 text-xs',
23
+ medium: 'px-3 py-2 text-sm',
24
+ large: 'px-4 py-3 text-base'
42
25
  }.freeze
43
26
 
44
- # Stati del select
45
- SELECT_STATE = {
46
- disabled: "bg-gray-100 cursor-not-allowed opacity-75"
27
+ SELECT_ROUNDED = {
28
+ none: 'rounded-none',
29
+ small: 'rounded-sm',
30
+ medium: 'rounded',
31
+ large: 'rounded-lg',
32
+ full: 'rounded-full'
47
33
  }.freeze
48
34
 
49
- # @param name [String] Name of the select field (required)
50
- # @param options [Array<Hash>] Array of options for the select in format [{value: 'value', label: 'label'}, ...]
51
- # @param selected [String, Array, nil] Selected value or values
52
- # @param required [Boolean] Whether the field is required
53
- # @param disabled [Boolean] Whether the field is disabled
54
- # @param multiple [Boolean] Whether multiple options can be selected
55
- # @param theme [Symbol] Component theme (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
56
- # @param size [Symbol] Component size (:small, :medium, :large)
35
+ SELECT_BASE_CLASSES = 'block w-full bg-white border-2 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors duration-200'.freeze
36
+
37
+ attr_reader :name, :options, :selected, :required, :disabled, :multiple, :searchable,
38
+ :theme, :size, :rounded, :placeholder, :search_placeholder, :max_height,
39
+ :form, :classes, :html_options
40
+
41
+ # @param name [String] Nome del campo select (obbligatorio)
42
+ # @param options [Array<Hash>] Array di opzioni nel formato [{value: 'value', label: 'label'}, ...]
43
+ # @param selected [String, Array, nil] Valore/i selezionato/i
44
+ # @param required [Boolean] Se il campo è obbligatorio
45
+ # @param disabled [Boolean] Se il campo è disabilitato
46
+ # @param multiple [Boolean] Se multiple opzioni possono essere selezionate
47
+ # @param searchable [Boolean] Se abilitare la ricerca
48
+ # @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
49
+ # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
57
50
  # @param rounded [Symbol] Border radius (:none, :small, :medium, :large, :full)
58
- # @param placeholder [String, nil] Placeholder text for the field (creates an initial disabled option)
59
- # @param form [ActionView::Helpers::FormBuilder, nil] Optional Rails form builder
60
- # @param options_html [Hash] Additional HTML attributes for option tags
61
- # @param classes [String] Additional CSS classes
62
- # @param html_options [Hash] Additional HTML attributes for the select tag
51
+ # @param placeholder [String] Testo placeholder per il trigger
52
+ # @param search_placeholder [String] Testo placeholder per il campo search
53
+ # @param max_height [String] Altezza massima del dropdown (default: "300px")
54
+ # @param form [ActionView::Helpers::FormBuilder, nil] Form builder Rails opzionale
55
+ # @param classes [String] Classi CSS aggiuntive
56
+ # @param html_options [Hash] Attributi HTML aggiuntivi
63
57
  def initialize(
64
58
  name:,
65
59
  options:,
66
60
  selected: nil,
67
61
  required: false,
68
62
  disabled: false,
69
- multiple: false,
63
+ multiple: false,
64
+ searchable: true,
70
65
  theme: :default,
71
66
  size: :medium,
72
67
  rounded: :medium,
73
68
  placeholder: nil,
69
+ search_placeholder: nil,
70
+ max_height: "300px",
74
71
  form: nil,
75
- options_html: {},
76
72
  classes: '',
77
73
  **html_options
78
74
  )
79
75
  @name = name
80
76
  @options = options
81
- @selected = selected
77
+ @selected = normalize_selected(selected)
82
78
  @required = required
83
79
  @disabled = disabled
84
80
  @multiple = multiple
81
+ @searchable = searchable
85
82
  @theme = theme.to_sym
86
83
  @size = size.to_sym
87
84
  @rounded = rounded.to_sym
88
- @placeholder = placeholder
85
+ @placeholder = placeholder || (multiple ? "Seleziona opzioni..." : "Seleziona...")
86
+ @search_placeholder = search_placeholder || "Cerca..."
87
+ @max_height = max_height
89
88
  @form = form
90
- @options_html = options_html
91
89
  @classes = classes
92
90
  @html_options = html_options
93
91
 
94
92
  validate_params
95
93
  end
96
94
 
97
- def combined_classes
95
+ private
96
+
97
+ def validate_params
98
+ validate_theme
99
+ validate_size
100
+ validate_rounded
101
+ end
102
+
103
+ def validate_theme
104
+ return if SELECT_THEME.key?(@theme)
105
+
106
+ raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{SELECT_THEME.keys.join(', ')}"
107
+ end
108
+
109
+ def validate_size
110
+ return if SELECT_SIZE.key?(@size)
111
+
112
+ raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{SELECT_SIZE.keys.join(', ')}"
113
+ end
114
+
115
+ def validate_rounded
116
+ return if SELECT_ROUNDED.key?(@rounded)
117
+
118
+ raise ArgumentError, "Invalid rounded: #{@rounded}. Valid rounded options are: #{SELECT_ROUNDED.keys.join(', ')}"
119
+ end
120
+
121
+ def normalize_selected(selected)
122
+ return [] if selected.nil?
123
+ return selected if selected.is_a?(Array)
124
+ [selected]
125
+ end
126
+
127
+ def container_attributes
128
+ {
129
+ 'data-controller': 'select',
130
+ 'data-select-multiple-value': @multiple,
131
+ 'data-select-searchable-value': @searchable,
132
+ 'data-select-selected-value': @selected.to_json,
133
+ 'data-select-placeholder-value': @placeholder,
134
+ 'data-select-search-placeholder-value': @search_placeholder,
135
+ 'data-action': 'keydown->select#keydown',
136
+ class: "relative #{@classes}"
137
+ }.merge(@html_options)
138
+ end
139
+
140
+ def trigger_classes
98
141
  [
99
142
  SELECT_BASE_CLASSES,
100
143
  SELECT_THEME[@theme],
101
144
  SELECT_SIZE[@size],
102
145
  SELECT_ROUNDED[@rounded],
103
- @disabled ? SELECT_STATE[:disabled] : nil,
104
- @classes
146
+ 'cursor-pointer flex items-center justify-between',
147
+ @disabled ? 'opacity-50 cursor-not-allowed' : 'hover:border-gray-400'
105
148
  ].compact.join(' ')
106
149
  end
107
150
 
108
- def select_attributes
109
- attrs = {
110
- name: form_field_name,
111
- id: @html_options[:id] || form_field_id,
112
- class: combined_classes
113
- }
151
+ def dropdown_classes
152
+ [
153
+ 'absolute z-50 w-full mt-1 bg-white border border-gray-300 shadow-lg',
154
+ @rounded == :none ? 'rounded-none' : 'rounded-md',
155
+ 'hidden opacity-0 scale-95 translate-y-1',
156
+ 'transition-all duration-150 ease-out'
157
+ ].join(' ')
158
+ end
114
159
 
115
- # Aggiungi attributi booleani solo se true
116
- attrs[:required] = @required if @required
117
- attrs[:disabled] = @disabled if @disabled
118
- attrs[:multiple] = @multiple if @multiple
160
+ def search_input_classes
161
+ [
162
+ 'w-full px-3 py-2 text-sm border-0 border-b border-gray-200',
163
+ 'focus:outline-none focus:border-gray-400 bg-gray-50'
164
+ ].join(' ')
165
+ end
119
166
 
120
- # Aggiungi altri attributi HTML
121
- @html_options.except(:id, :class).each do |key, value|
122
- attrs[key] = value unless value.nil?
123
- end
124
-
125
- # Rimuovi attributi con valori nil o false
126
- attrs.compact
167
+ def options_container_classes
168
+ [
169
+ 'overflow-y-auto',
170
+ "max-h-[#{@max_height}]"
171
+ ].join(' ')
172
+ end
173
+
174
+ def option_classes
175
+ [
176
+ 'px-3 py-2 cursor-pointer hover:bg-gray-50 flex items-center justify-between',
177
+ 'transition-colors duration-150'
178
+ ].join(' ')
127
179
  end
128
180
 
129
- def form_field_name
181
+ def input_name
130
182
  if @form.present?
131
183
  @form.field_name(@name)
132
184
  else
133
- @name
185
+ @multiple ? "#{@name}[]" : @name
134
186
  end
135
187
  end
136
188
 
137
- def form_field_id
189
+ def input_id
138
190
  if @form.present?
139
191
  @form.field_id(@name)
140
192
  else
@@ -142,43 +194,56 @@ module BetterUi
142
194
  end
143
195
  end
144
196
 
145
- def selected?(option)
146
- return false if @selected.nil?
147
-
148
- if @selected.is_a?(Array)
149
- @selected.include?(option[:value])
197
+ def hidden_input_value
198
+ if @multiple
199
+ @selected.to_json
150
200
  else
151
- option[:value].to_s == @selected.to_s
201
+ @selected.first || ''
152
202
  end
153
203
  end
154
204
 
155
- private
205
+ def trigger_text
206
+ if @selected.empty?
207
+ @placeholder
208
+ elsif @multiple
209
+ case @selected.length
210
+ when 1
211
+ selected_option = @options.find { |opt| opt[:value].to_s == @selected.first.to_s }
212
+ selected_option ? selected_option[:label] : @selected.first
213
+ else
214
+ "#{@selected.length} selezionati"
215
+ end
216
+ else
217
+ selected_option = @options.find { |opt| opt[:value].to_s == @selected.first.to_s }
218
+ selected_option ? selected_option[:label] : @selected.first
219
+ end
220
+ end
156
221
 
157
- def validate_params
158
- validate_theme
159
- validate_size
160
- validate_rounded
222
+ def trigger_text_classes
223
+ @selected.empty? ? 'text-gray-500' : 'text-gray-900'
161
224
  end
162
-
163
- def validate_theme
164
- unless SELECT_THEME.keys.include?(@theme)
165
- raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{SELECT_THEME.keys.join(', ')}"
166
- end
225
+
226
+ def selected_option_values
227
+ @selected.map(&:to_s)
167
228
  end
168
-
169
- def validate_size
170
- unless SELECT_SIZE.keys.include?(@size)
171
- raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{SELECT_SIZE.keys.join(', ')}"
172
- end
229
+
230
+ def option_selected?(option)
231
+ selected_option_values.include?(option[:value].to_s)
173
232
  end
174
-
175
- def validate_rounded
176
- unless SELECT_ROUNDED.keys.include?(@rounded)
177
- raise ArgumentError, "Invalid rounded: #{@rounded}. Valid rounded options are: #{SELECT_ROUNDED.keys.join(', ')}"
178
- end
233
+
234
+ def badge_container_classes
235
+ 'flex flex-wrap gap-1 mt-2'
236
+ end
237
+
238
+ def chevron_icon
239
+ '
240
+ <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
241
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
242
+ </svg>
243
+ '.html_safe
179
244
  end
180
245
  end
181
246
  end
182
247
  end
183
248
  end
184
- end
249
+ end
@@ -0,0 +1,5 @@
1
+ <% if @label %>
2
+ <%= render_toggle_with_label %>
3
+ <% else %>
4
+ <%= render_toggle_switch %>
5
+ <% end %>
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Input
6
+ module Toggle
7
+ class Component < ViewComponent::Base
8
+ # Costanti con classi Tailwind dirette
9
+ TOGGLE_THEME = {
10
+ default: 'peer-checked:bg-gray-800',
11
+ white: 'peer-checked:bg-white border peer-checked:border-gray-900',
12
+ red: 'peer-checked:bg-red-600',
13
+ rose: 'peer-checked:bg-rose-600',
14
+ orange: 'peer-checked:bg-orange-600',
15
+ green: 'peer-checked:bg-green-600',
16
+ blue: 'peer-checked:bg-blue-600',
17
+ yellow: 'peer-checked:bg-yellow-600',
18
+ violet: 'peer-checked:bg-violet-600'
19
+ }.freeze
20
+
21
+ TOGGLE_TRACK_SIZE = {
22
+ small: 'w-9 h-5',
23
+ medium: 'w-11 h-6',
24
+ large: 'w-14 h-7'
25
+ }.freeze
26
+
27
+ TOGGLE_THUMB_SIZE = {
28
+ small: 'w-4 h-4',
29
+ medium: 'w-5 h-5',
30
+ large: 'w-6 h-6'
31
+ }.freeze
32
+
33
+ TOGGLE_THUMB_POSITION = {
34
+ small: 'peer-checked:translate-x-4',
35
+ medium: 'peer-checked:translate-x-5',
36
+ large: 'peer-checked:translate-x-7'
37
+ }.freeze
38
+
39
+ TOGGLE_BASE_TRACK = 'relative bg-gray-300 rounded-full transition-colors duration-200 ease-in-out'.freeze
40
+ TOGGLE_BASE_THUMB = 'bg-white rounded-full shadow-sm transform transition-transform duration-200 ease-in-out absolute top-0.5 left-0.5'.freeze
41
+
42
+ TOGGLE_LABEL_GAP = {
43
+ small: 'gap-2',
44
+ medium: 'gap-2.5',
45
+ large: 'gap-3'
46
+ }.freeze
47
+
48
+ TOGGLE_LABEL_TEXT = {
49
+ small: 'text-sm',
50
+ medium: 'text-base',
51
+ large: 'text-lg'
52
+ }.freeze
53
+
54
+ attr_reader :name, :value, :checked, :required, :disabled,
55
+ :label, :label_position, :theme, :size,
56
+ :form, :classes, :options
57
+
58
+ # @param name [String] Nome del campo toggle (obbligatorio)
59
+ # @param value [String] Valore del toggle quando è attivo (default: "1")
60
+ # @param checked [Boolean] Se il toggle è attivo
61
+ # @param required [Boolean] Se il campo è obbligatorio
62
+ # @param disabled [Boolean] Se il campo è disabilitato
63
+ # @param label [String, nil] Testo della label associata al toggle
64
+ # @param label_position [Symbol] Posizione della label (:left, :right)
65
+ # @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
66
+ # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
67
+ # @param form [ActionView::Helpers::FormBuilder, nil] Form builder Rails opzionale
68
+ # @param classes [String] Classi CSS aggiuntive
69
+ # @param options [Hash] Opzioni aggiuntive per l'input (es. data attributes)
70
+ def initialize(name:, value: '1', checked: false, required: false, disabled: false,
71
+ label: nil, label_position: :right, theme: :default,
72
+ size: :medium, form: nil, classes: '', **options)
73
+ @name = name
74
+ @value = value
75
+ @checked = checked
76
+ @required = required
77
+ @disabled = disabled
78
+ @label = label
79
+ @label_position = label_position.to_sym
80
+ @theme = theme.to_sym
81
+ @size = size.to_sym
82
+ @form = form
83
+ @classes = classes
84
+ @options = options
85
+
86
+ validate_params
87
+ end
88
+
89
+ private
90
+
91
+ def validate_params
92
+ validate_theme
93
+ validate_size
94
+ validate_label_position
95
+ end
96
+
97
+ def validate_theme
98
+ return if TOGGLE_THEME.key?(@theme)
99
+
100
+ raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{TOGGLE_THEME.keys.join(', ')}"
101
+ end
102
+
103
+ def validate_size
104
+ return if TOGGLE_TRACK_SIZE.key?(@size)
105
+
106
+ raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{TOGGLE_TRACK_SIZE.keys.join(', ')}"
107
+ end
108
+
109
+ def validate_label_position
110
+ return if [:left, :right].include?(@label_position)
111
+
112
+ raise ArgumentError, "Invalid label_position: #{@label_position}. Valid positions are: left, right"
113
+ end
114
+
115
+ def container_classes
116
+ base_classes = ['inline-flex', 'items-center']
117
+ base_classes << TOGGLE_LABEL_GAP[@size]
118
+ base_classes << (@disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer')
119
+ base_classes << @classes if @classes.present?
120
+ base_classes.join(' ')
121
+ end
122
+
123
+ def track_classes
124
+ [
125
+ TOGGLE_BASE_TRACK,
126
+ TOGGLE_THEME[@theme],
127
+ TOGGLE_TRACK_SIZE[@size]
128
+ ].join(' ')
129
+ end
130
+
131
+ def thumb_classes
132
+ [
133
+ TOGGLE_BASE_THUMB,
134
+ TOGGLE_THUMB_SIZE[@size],
135
+ TOGGLE_THUMB_POSITION[@size]
136
+ ].join(' ')
137
+ end
138
+
139
+ def input_attributes
140
+ attrs = {
141
+ type: 'checkbox',
142
+ name: input_name,
143
+ value: @value,
144
+ class: 'sr-only peer',
145
+ checked: @checked,
146
+ required: @required,
147
+ disabled: @disabled,
148
+ id: input_id
149
+ }
150
+
151
+ attrs.merge(@options)
152
+ end
153
+
154
+ def input_name
155
+ if @form
156
+ @form.field_name(@name)
157
+ else
158
+ @name
159
+ end
160
+ end
161
+
162
+ def input_id
163
+ @options[:id] || "toggle_#{@name}"
164
+ end
165
+
166
+ def label_classes
167
+ TOGGLE_LABEL_TEXT[@size]
168
+ end
169
+
170
+ def input_tag
171
+ if @form
172
+ form_checkbox
173
+ else
174
+ manual_input
175
+ end
176
+ end
177
+
178
+ def form_checkbox
179
+ @form.check_box(@name, {
180
+ class: 'sr-only peer',
181
+ id: input_id,
182
+ checked: @checked,
183
+ disabled: @disabled,
184
+ required: @required,
185
+ **@options
186
+ }, @value, '0')
187
+ end
188
+
189
+ def manual_input
190
+ attrs = input_attributes.map do |key, value|
191
+ if value == true
192
+ key.to_s
193
+ elsif value == false || value.nil?
194
+ nil
195
+ else
196
+ "#{key}=\"#{value}\""
197
+ end
198
+ end.compact.join(' ')
199
+
200
+ "<input #{attrs} />".html_safe
201
+ end
202
+
203
+ def render_toggle_with_label
204
+ if @label_position == :left
205
+ label_left_content
206
+ else
207
+ label_right_content
208
+ end
209
+ end
210
+
211
+ def label_left_content
212
+ content_tag(:label, class: container_classes, for: input_id) do
213
+ safe_join([
214
+ content_tag(:span, @label, class: label_classes),
215
+ render_toggle_switch
216
+ ])
217
+ end
218
+ end
219
+
220
+ def label_right_content
221
+ content_tag(:label, class: container_classes, for: input_id) do
222
+ safe_join([
223
+ render_toggle_switch,
224
+ content_tag(:span, @label, class: label_classes)
225
+ ])
226
+ end
227
+ end
228
+
229
+ def render_toggle_switch
230
+ content_tag(:div, class: 'relative') do
231
+ safe_join([
232
+ input_tag,
233
+ content_tag(:div, '', class: track_classes),
234
+ content_tag(:div, '', class: thumb_classes)
235
+ ])
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -36,7 +36,7 @@ module BetterUi
36
36
 
37
37
  # Dimensioni con classi Tailwind dirette
38
38
  LINK_SIZE_CLASSES = {
39
- extra_small: "text-[0.65rem]",
39
+ extra_small: "text-xs",
40
40
  small: "text-sm",
41
41
  medium: "text-base",
42
42
  large: "text-lg"
@@ -1,42 +1,5 @@
1
- <%# Template per il modal %>
2
- <% if @backdrop %>
3
- <%= tag.div **backdrop_attributes do %>
4
- <%= tag.div **container_attributes do %>
5
- <%# Header del modal %>
6
- <%= tag.div **header_attributes do %>
7
- <h3 class="text-lg font-semibold" id="modal-title"><%= @title %></h3>
8
- <% if @closable %>
9
- <button type="button" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 transition-colors duration-200" aria-label="Chiudi modal">
10
- <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
11
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
12
- </svg>
13
- </button>
14
- <% end %>
15
- <% end %>
16
-
17
- <%# Body del modal %>
18
- <div class="p-6">
19
- <%= content %>
20
- </div>
21
- <% end %>
22
- <% end %>
23
- <% else %>
24
- <%= tag.div **container_attributes do %>
25
- <%# Header del modal %>
26
- <%= tag.div **header_attributes do %>
27
- <h3 class="text-lg font-semibold" id="modal-title"><%= @title %></h3>
28
- <% if @closable %>
29
- <button type="button" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 transition-colors duration-200" aria-label="Chiudi modal">
30
- <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
31
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
32
- </svg>
33
- </button>
34
- <% end %>
35
- <% end %>
36
-
37
- <%# Body del modal %>
38
- <div class="p-6">
39
- <%= content %>
40
- </div>
41
- <% end %>
42
- <% end %>
1
+ <%# Wrapper component con controller Stimulus e slots %>
2
+ <div <%= tag.attributes(wrapper_attributes) %>>
3
+ <%= trigger %>
4
+ <%= modal %>
5
+ </div>