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.
- checksums.yaml +4 -4
- data/app/components/better_ui/application/main/component.html.erb +1 -1
- data/app/components/better_ui/application/sidebar/component.html.erb +77 -18
- data/app/components/better_ui/application/sidebar/component.rb +63 -5
- data/app/components/better_ui/general/accordion/component.html.erb +5 -0
- data/app/components/better_ui/general/accordion/component.rb +92 -0
- data/app/components/better_ui/general/accordion/item_component.html.erb +12 -0
- data/app/components/better_ui/general/accordion/item_component.rb +176 -0
- data/app/components/better_ui/general/button/component.html.erb +8 -8
- data/app/components/better_ui/general/button/component.rb +11 -11
- data/app/components/better_ui/general/dropdown/component.html.erb +21 -7
- data/app/components/better_ui/general/dropdown/component.rb +27 -54
- data/app/components/better_ui/general/dropdown/item_component.rb +2 -1
- data/app/components/better_ui/general/field/component.html.erb +3 -3
- data/app/components/better_ui/general/field/component.rb +3 -3
- data/app/components/better_ui/general/grid/cell_component.html.erb +3 -0
- data/app/components/better_ui/general/grid/cell_component.rb +390 -0
- data/app/components/better_ui/general/grid/component.html.erb +3 -0
- data/app/components/better_ui/general/grid/component.rb +301 -0
- data/app/components/better_ui/general/heading/component.html.erb +1 -1
- data/app/components/better_ui/general/icon/component.rb +2 -1
- data/app/components/better_ui/general/input/checkbox/component.rb +10 -10
- data/app/components/better_ui/general/input/pin/component.html.erb +1 -0
- data/app/components/better_ui/general/input/pin/component.rb +201 -0
- data/app/components/better_ui/general/input/radio/component.rb +10 -10
- data/app/components/better_ui/general/input/rating/component.html.erb +4 -0
- data/app/components/better_ui/general/input/rating/component.rb +272 -0
- data/app/components/better_ui/general/input/select/component.html.erb +76 -14
- data/app/components/better_ui/general/input/select/component.rb +166 -101
- data/app/components/better_ui/general/input/toggle/component.html.erb +5 -0
- data/app/components/better_ui/general/input/toggle/component.rb +242 -0
- data/app/components/better_ui/general/link/component.rb +1 -1
- data/app/components/better_ui/general/modal/component.html.erb +5 -42
- data/app/components/better_ui/general/modal/component.rb +22 -140
- data/app/components/better_ui/general/modal/modal_component.html.erb +52 -0
- data/app/components/better_ui/general/modal/modal_component.rb +160 -0
- data/app/components/better_ui/general/tabs/component.html.erb +10 -2
- data/app/components/better_ui/general/tabs/component.rb +26 -8
- data/app/components/better_ui/general/tabs/panel_component.rb +1 -1
- data/app/components/better_ui/general/tabs/tab_component.rb +1 -1
- data/app/components/better_ui/general/text/component.html.erb +1 -0
- data/app/components/better_ui/general/text/component.rb +194 -0
- data/app/helpers/better_ui/application_helper.rb +11 -4
- data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +73 -0
- data/app/helpers/better_ui/general/components/button/button_helper.rb +6 -6
- data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +9 -0
- data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +13 -7
- data/app/helpers/better_ui/general/components/field/field_helper.rb +4 -4
- data/app/helpers/better_ui/general/components/grid/grid_helper.rb +145 -0
- data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +76 -0
- data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +70 -0
- data/app/helpers/better_ui/general/components/input/select/select_helper.rb +47 -31
- data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +77 -0
- data/app/helpers/better_ui/general/components/modal/modal_helper.rb +34 -44
- data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +59 -26
- data/app/helpers/better_ui/general/components/text/text_helper.rb +83 -0
- data/lib/better_ui/version.rb +1 -1
- data/lib/better_ui.rb +1 -0
- metadata +26 -2
@@ -5,136 +5,188 @@ module BetterUi
|
|
5
5
|
module Input
|
6
6
|
module Select
|
7
7
|
class Component < ViewComponent::Base
|
8
|
-
|
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:
|
18
|
-
white:
|
19
|
-
red:
|
20
|
-
rose:
|
21
|
-
orange:
|
22
|
-
green:
|
23
|
-
blue:
|
24
|
-
yellow:
|
25
|
-
violet:
|
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:
|
31
|
-
medium:
|
32
|
-
large:
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
# @param
|
56
|
-
# @param
|
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
|
59
|
-
# @param
|
60
|
-
# @param
|
61
|
-
# @param
|
62
|
-
# @param
|
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
|
-
|
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
|
-
|
104
|
-
@
|
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
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
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
|
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
|
146
|
-
|
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
|
-
|
201
|
+
@selected.first || ''
|
152
202
|
end
|
153
203
|
end
|
154
204
|
|
155
|
-
|
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
|
158
|
-
|
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
|
164
|
-
|
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
|
170
|
-
|
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
|
176
|
-
|
177
|
-
|
178
|
-
|
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,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
|
@@ -1,42 +1,5 @@
|
|
1
|
-
<%#
|
2
|
-
|
3
|
-
<%=
|
4
|
-
|
5
|
-
|
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>
|