better_ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +225 -119
- data/app/assets/stylesheets/better_ui/application.css +0 -356
- data/app/components/better_ui/application/card/component.html.erb +20 -0
- data/app/components/better_ui/application/card/component.rb +214 -0
- data/app/components/better_ui/application/main/component.html.erb +9 -0
- data/app/components/better_ui/application/main/component.rb +123 -0
- data/app/components/better_ui/application/navbar/component.html.erb +92 -0
- data/app/components/better_ui/application/navbar/component.rb +136 -0
- data/app/components/better_ui/application/sidebar/component.html.erb +190 -0
- data/app/components/better_ui/application/sidebar/component.rb +129 -0
- data/app/components/better_ui/general/alert/component.html.erb +32 -0
- data/app/components/better_ui/general/alert/component.rb +242 -0
- data/app/components/better_ui/general/avatar/component.html.erb +20 -0
- data/app/components/better_ui/general/avatar/component.rb +301 -0
- data/app/components/better_ui/general/badge/component.html.erb +23 -0
- data/app/components/better_ui/general/badge/component.rb +248 -0
- data/app/components/better_ui/general/breadcrumb/component.html.erb +15 -0
- data/app/components/better_ui/general/breadcrumb/component.rb +187 -0
- data/app/components/better_ui/general/button/component.html.erb +34 -0
- data/app/components/better_ui/general/button/component.rb +214 -0
- data/app/components/better_ui/general/divider/component.html.erb +10 -0
- data/app/components/better_ui/general/divider/component.rb +226 -0
- data/app/components/better_ui/general/dropdown/component.html.erb +14 -0
- data/app/components/better_ui/general/dropdown/component.rb +219 -0
- data/app/components/better_ui/general/dropdown/divider_component.html.erb +1 -0
- data/app/components/better_ui/general/dropdown/divider_component.rb +41 -0
- data/app/components/better_ui/general/dropdown/item_component.html.erb +6 -0
- data/app/components/better_ui/general/dropdown/item_component.rb +118 -0
- data/app/components/better_ui/general/field/component.html.erb +27 -0
- data/app/components/better_ui/general/field/component.rb +37 -0
- data/app/components/better_ui/general/heading/component.html.erb +22 -0
- data/app/components/better_ui/general/heading/component.rb +257 -0
- data/app/components/better_ui/general/icon/component.html.erb +7 -0
- data/app/components/better_ui/general/icon/component.rb +239 -0
- data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
- data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
- data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
- data/app/components/better_ui/general/input/datetime/component.rb +223 -0
- data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
- data/app/components/better_ui/general/input/radio/component.rb +230 -0
- data/app/components/better_ui/general/input/select/component.html.erb +16 -0
- data/app/components/better_ui/general/input/select/component.rb +184 -0
- data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
- data/app/components/better_ui/general/input/select/select_component.rb +37 -0
- data/app/components/better_ui/general/input/text/component.html.erb +5 -0
- data/app/components/better_ui/general/input/text/component.rb +171 -0
- data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
- data/app/components/better_ui/general/input/textarea/component.rb +166 -0
- data/app/components/better_ui/general/link/component.html.erb +18 -0
- data/app/components/better_ui/general/link/component.rb +258 -0
- data/app/components/better_ui/general/modal/component.html.erb +42 -0
- data/app/components/better_ui/general/modal/component.rb +165 -0
- data/app/components/better_ui/general/pagination/component.html.erb +85 -0
- data/app/components/better_ui/general/pagination/component.rb +216 -0
- data/app/components/better_ui/general/panel/component.html.erb +28 -0
- data/app/components/better_ui/general/panel/component.rb +249 -0
- data/app/components/better_ui/general/progress/component.html.erb +11 -0
- data/app/components/better_ui/general/progress/component.rb +160 -0
- data/app/components/better_ui/general/spinner/component.html.erb +35 -0
- data/app/components/better_ui/general/spinner/component.rb +93 -0
- data/app/components/better_ui/general/table/component.html.erb +5 -0
- data/app/components/better_ui/general/table/component.rb +217 -0
- data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
- data/app/components/better_ui/general/table/tbody_component.rb +30 -0
- data/app/components/better_ui/general/table/td_component.html.erb +3 -0
- data/app/components/better_ui/general/table/td_component.rb +44 -0
- data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
- data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
- data/app/components/better_ui/general/table/th_component.html.erb +6 -0
- data/app/components/better_ui/general/table/th_component.rb +51 -0
- data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
- data/app/components/better_ui/general/table/thead_component.rb +28 -0
- data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
- data/app/components/better_ui/general/table/tr_component.rb +30 -0
- data/app/components/better_ui/general/tabs/component.html.erb +3 -0
- data/app/components/better_ui/general/tabs/component.rb +102 -0
- data/app/components/better_ui/general/tabs/panel_component.html.erb +3 -0
- data/app/components/better_ui/general/tabs/panel_component.rb +37 -0
- data/app/components/better_ui/general/tabs/tab_component.html.erb +13 -0
- data/app/components/better_ui/general/tabs/tab_component.rb +111 -0
- data/app/components/better_ui/general/tag/component.html.erb +3 -0
- data/app/components/better_ui/general/tag/component.rb +104 -0
- data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
- data/app/components/better_ui/general/tooltip/component.rb +239 -0
- data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
- data/app/helpers/better_ui/application/components/card.rb +11 -0
- data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
- data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
- data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
- data/app/helpers/better_ui/application_helper.rb +51 -179
- data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
- data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
- data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
- data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
- data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
- data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
- data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
- data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +32 -0
- data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +79 -0
- data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +62 -0
- data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
- data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
- data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
- data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
- data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
- data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
- data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
- data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
- data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
- data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
- data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
- data/app/helpers/better_ui/general/components/modal/modal_helper.rb +95 -0
- data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +82 -0
- data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
- data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
- data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
- data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
- data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
- data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
- data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +62 -0
- data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +55 -0
- data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +62 -0
- data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
- data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
- data/app/views/layouts/better_ui/application.html.erb +6 -124
- data/config/initializers/lookbook.rb +23 -0
- data/config/routes.rb +0 -8
- data/lib/better_ui/engine.rb +5 -19
- data/lib/better_ui/railtie.rb +20 -0
- data/lib/better_ui/version.rb +1 -1
- data/lib/better_ui.rb +4 -20
- metadata +155 -28
- data/app/controllers/better_ui/docs_controller.rb +0 -41
- data/app/views/better_ui/docs/component.html.erb +0 -365
- data/app/views/better_ui/docs/index.html.erb +0 -100
- data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module General
|
5
|
+
module Input
|
6
|
+
module Checkbox
|
7
|
+
class Component < ViewComponent::Base
|
8
|
+
# Costanti con classi Tailwind dirette
|
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',
|
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
|
+
CHECKBOX_SIZE = {
|
22
|
+
small: 'h-4 w-4',
|
23
|
+
medium: 'h-5 w-5',
|
24
|
+
large: 'h-6 w-6'
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
CHECKBOX_ROUNDED = {
|
28
|
+
none: 'rounded-none',
|
29
|
+
small: 'rounded-sm',
|
30
|
+
medium: 'rounded',
|
31
|
+
large: 'rounded-lg',
|
32
|
+
full: 'rounded-full'
|
33
|
+
}.freeze
|
34
|
+
|
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
|
+
|
37
|
+
CHECKBOX_LABEL_GAP = {
|
38
|
+
small: 'gap-2',
|
39
|
+
medium: 'gap-2.5',
|
40
|
+
large: 'gap-3'
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
CHECKBOX_LABEL_TEXT = {
|
44
|
+
small: 'text-sm',
|
45
|
+
medium: 'text-base',
|
46
|
+
large: 'text-lg'
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
attr_reader :name, :value, :checked, :required, :disabled, :indeterminate,
|
50
|
+
:label, :label_position, :theme, :size, :rounded, :classes, :form, :options
|
51
|
+
|
52
|
+
# @param name [String] Nome del campo checkbox (obbligatorio)
|
53
|
+
# @param value [String] Valore del checkbox (default: "1")
|
54
|
+
# @param checked [Boolean] Se il checkbox è selezionato
|
55
|
+
# @param required [Boolean] Se il campo è obbligatorio
|
56
|
+
# @param disabled [Boolean] Se il campo è disabilitato
|
57
|
+
# @param indeterminate [Boolean] Se il checkbox è in stato indeterminate
|
58
|
+
# @param label [String, nil] Testo della label associata al checkbox
|
59
|
+
# @param label_position [Symbol] Posizione della label (:left, :right)
|
60
|
+
# @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
|
61
|
+
# @param size [Symbol] Dimensione del componente (:small, :medium, :large)
|
62
|
+
# @param rounded [Symbol] Border radius (:none, :small, :medium, :large, :full)
|
63
|
+
# @param classes [String] Classi CSS aggiuntive
|
64
|
+
# @param form [ActionView::Helpers::FormBuilder, nil] Form builder Rails opzionale
|
65
|
+
# @param options [Hash] Opzioni aggiuntive per l'input (es. data attributes, aria attributes)
|
66
|
+
def initialize(name:, value: "1", checked: false, required: false, disabled: false,
|
67
|
+
indeterminate: false, label: nil, label_position: :right, theme: :default,
|
68
|
+
size: :medium, rounded: :medium, classes: '', form: nil, **options)
|
69
|
+
@name = name
|
70
|
+
@value = value
|
71
|
+
@checked = checked
|
72
|
+
@required = required
|
73
|
+
@disabled = disabled
|
74
|
+
@indeterminate = indeterminate
|
75
|
+
@label = label
|
76
|
+
@label_position = label_position.to_sym
|
77
|
+
@theme = theme.to_sym
|
78
|
+
@size = size.to_sym
|
79
|
+
@rounded = rounded.to_sym
|
80
|
+
@classes = classes
|
81
|
+
@form = form
|
82
|
+
@options = options
|
83
|
+
|
84
|
+
validate_params
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def validate_params
|
90
|
+
validate_theme
|
91
|
+
validate_size
|
92
|
+
validate_rounded
|
93
|
+
validate_label_position
|
94
|
+
end
|
95
|
+
|
96
|
+
def validate_theme
|
97
|
+
return if CHECKBOX_THEME.key?(@theme)
|
98
|
+
|
99
|
+
raise ArgumentError, "Invalid theme: #{@theme}. Valid themes are: #{CHECKBOX_THEME.keys.join(', ')}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_size
|
103
|
+
return if CHECKBOX_SIZE.key?(@size)
|
104
|
+
|
105
|
+
raise ArgumentError, "Invalid size: #{@size}. Valid sizes are: #{CHECKBOX_SIZE.keys.join(', ')}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def validate_rounded
|
109
|
+
return if CHECKBOX_ROUNDED.key?(@rounded)
|
110
|
+
|
111
|
+
raise ArgumentError, "Invalid rounded: #{@rounded}. Valid rounded options are: #{CHECKBOX_ROUNDED.keys.join(', ')}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_label_position
|
115
|
+
return if [:left, :right].include?(@label_position)
|
116
|
+
|
117
|
+
raise ArgumentError, "Invalid label_position: #{@label_position}. Valid positions are: left, right"
|
118
|
+
end
|
119
|
+
|
120
|
+
def checkbox_classes
|
121
|
+
[
|
122
|
+
CHECKBOX_BASE_CLASSES,
|
123
|
+
CHECKBOX_THEME[@theme],
|
124
|
+
CHECKBOX_SIZE[@size],
|
125
|
+
CHECKBOX_ROUNDED[@rounded],
|
126
|
+
@classes
|
127
|
+
].compact.join(' ')
|
128
|
+
end
|
129
|
+
|
130
|
+
def input_attributes
|
131
|
+
attrs = {
|
132
|
+
type: 'checkbox',
|
133
|
+
name: input_name,
|
134
|
+
value: @value,
|
135
|
+
class: checkbox_classes,
|
136
|
+
checked: @checked,
|
137
|
+
required: @required,
|
138
|
+
disabled: @disabled,
|
139
|
+
id: input_id
|
140
|
+
}
|
141
|
+
|
142
|
+
# Aggiungi indeterminate via JavaScript se necessario
|
143
|
+
if @indeterminate
|
144
|
+
attrs['data-indeterminate'] = 'true'
|
145
|
+
end
|
146
|
+
|
147
|
+
# Unisci le opzioni personalizzate
|
148
|
+
attrs.merge(@options)
|
149
|
+
end
|
150
|
+
|
151
|
+
def input_name
|
152
|
+
if @form
|
153
|
+
@form.field_name(@name)
|
154
|
+
else
|
155
|
+
@name
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def input_id
|
160
|
+
@options[:id] || "checkbox_#{@name}"
|
161
|
+
end
|
162
|
+
|
163
|
+
def label_classes
|
164
|
+
[
|
165
|
+
'flex items-center cursor-pointer',
|
166
|
+
@disabled ? 'opacity-50 cursor-not-allowed' : '',
|
167
|
+
CHECKBOX_LABEL_GAP[@size]
|
168
|
+
].compact.join(' ')
|
169
|
+
end
|
170
|
+
|
171
|
+
def label_text_classes
|
172
|
+
CHECKBOX_LABEL_TEXT[@size]
|
173
|
+
end
|
174
|
+
|
175
|
+
def input_tag
|
176
|
+
if @form
|
177
|
+
form_checkbox
|
178
|
+
else
|
179
|
+
manual_input
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def form_checkbox
|
184
|
+
@form.check_box(@name, {
|
185
|
+
class: checkbox_classes,
|
186
|
+
id: input_id,
|
187
|
+
checked: @checked,
|
188
|
+
disabled: @disabled,
|
189
|
+
required: @required,
|
190
|
+
data: @indeterminate ? { indeterminate: 'true' } : {},
|
191
|
+
**@options
|
192
|
+
}, @value)
|
193
|
+
end
|
194
|
+
|
195
|
+
def manual_input
|
196
|
+
attrs = input_attributes.map do |key, value|
|
197
|
+
if value == true
|
198
|
+
key.to_s
|
199
|
+
elsif value == false || value.nil?
|
200
|
+
nil
|
201
|
+
else
|
202
|
+
"#{key}=\"#{value}\""
|
203
|
+
end
|
204
|
+
end.compact.join(' ')
|
205
|
+
|
206
|
+
"<input #{attrs} />".html_safe
|
207
|
+
end
|
208
|
+
|
209
|
+
def render_checkbox_with_label
|
210
|
+
if @label_position == :left
|
211
|
+
label_left_content
|
212
|
+
else
|
213
|
+
label_right_content
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def label_left_content
|
218
|
+
content_tag(:label, class: label_classes, for: input_id) do
|
219
|
+
safe_join([
|
220
|
+
content_tag(:span, @label, class: label_text_classes),
|
221
|
+
input_tag
|
222
|
+
])
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def label_right_content
|
227
|
+
content_tag(:label, class: label_classes, for: input_id) do
|
228
|
+
safe_join([
|
229
|
+
input_tag,
|
230
|
+
content_tag(:span, @label, class: label_text_classes)
|
231
|
+
])
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module General
|
5
|
+
module Input
|
6
|
+
module Datetime
|
7
|
+
class Component < ViewComponent::Base
|
8
|
+
attr_reader :name, :value, :required, :disabled, :classes, :options,
|
9
|
+
:theme, :size, :rounded, :form, :min, :max, :type
|
10
|
+
|
11
|
+
# Temi supportati per il Datetime Input
|
12
|
+
DATETIME_INPUT_THEME = {
|
13
|
+
default: 'border-gray-300 focus:border-blue-500 focus:ring-blue-500',
|
14
|
+
white: 'border-white focus:border-gray-300 focus:ring-gray-300 bg-white',
|
15
|
+
red: 'border-red-300 focus:border-red-500 focus:ring-red-500',
|
16
|
+
rose: 'border-rose-300 focus:border-rose-500 focus:ring-rose-500',
|
17
|
+
orange: 'border-orange-300 focus:border-orange-500 focus:ring-orange-500',
|
18
|
+
green: 'border-green-300 focus:border-green-500 focus:ring-green-500',
|
19
|
+
blue: 'border-blue-300 focus:border-blue-500 focus:ring-blue-500',
|
20
|
+
yellow: 'border-yellow-300 focus:border-yellow-500 focus:ring-yellow-500',
|
21
|
+
violet: 'border-violet-300 focus:border-violet-500 focus:ring-violet-500'
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
# Dimensioni supportate per il Datetime Input
|
25
|
+
DATETIME_INPUT_SIZES = {
|
26
|
+
small: 'h-8 px-2 py-1 text-xs',
|
27
|
+
medium: 'h-10 px-3 py-2 text-sm',
|
28
|
+
large: 'h-12 px-4 py-3 text-base'
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
# Border radius supportati per il Datetime Input
|
32
|
+
DATETIME_INPUT_RADIUS = {
|
33
|
+
none: 'rounded-none',
|
34
|
+
small: 'rounded-sm',
|
35
|
+
medium: 'rounded-md',
|
36
|
+
large: 'rounded-lg',
|
37
|
+
full: 'rounded-full'
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
# Tipi supportati per il Datetime Input
|
41
|
+
DATETIME_INPUT_TYPES = [
|
42
|
+
:date, :month, :week, :time
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
# Formati di validazione per tipo
|
46
|
+
DATETIME_FORMAT_PATTERNS = {
|
47
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
48
|
+
month: /^\d{4}-\d{2}$/,
|
49
|
+
week: /^\d{4}-W\d{2}$/,
|
50
|
+
time: /^\d{2}:\d{2}$/
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
# Classi base per il Datetime Input
|
54
|
+
DATETIME_INPUT_BASE_CLASSES = 'block w-full border shadow-sm disabled:bg-gray-100 disabled:cursor-not-allowed focus:outline-none focus:ring-1'
|
55
|
+
|
56
|
+
# @param name [String] Nome del campo input
|
57
|
+
# @param type [Symbol] Tipo del campo datetime (:date, :month, :week, :time)
|
58
|
+
# @param value [String] Valore del campo nel formato appropriato per il tipo
|
59
|
+
# @param required [Boolean] Se il campo è obbligatorio
|
60
|
+
# @param disabled [Boolean] Se il campo è disabilitato
|
61
|
+
# @param min [String] Valore minimo selezionabile nel formato appropriato
|
62
|
+
# @param max [String] Valore massimo selezionabile nel formato appropriato
|
63
|
+
# @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
|
64
|
+
# @param size [Symbol] Dimensione del componente (:small, :medium, :large)
|
65
|
+
# @param rounded [Symbol] Border radius (:none, :small, :medium, :large, :full)
|
66
|
+
# @param classes [String] Classi CSS aggiuntive
|
67
|
+
# @param form [ActionView::Helpers::FormBuilder] Form builder Rails opzionale
|
68
|
+
# @param options [Hash] Opzioni aggiuntive per l'input
|
69
|
+
def initialize(name:, type: :date, value: nil, required: false, disabled: false,
|
70
|
+
min: nil, max: nil, theme: :default, size: :medium, rounded: :medium, classes: '', form: nil, **options)
|
71
|
+
@name = name
|
72
|
+
@type = type
|
73
|
+
@value = value
|
74
|
+
@required = required
|
75
|
+
@disabled = disabled
|
76
|
+
@min = min
|
77
|
+
@max = max
|
78
|
+
@theme = theme
|
79
|
+
@size = size
|
80
|
+
@rounded = rounded
|
81
|
+
@classes = classes
|
82
|
+
@form = form
|
83
|
+
@options = options
|
84
|
+
|
85
|
+
validate_params
|
86
|
+
super()
|
87
|
+
end
|
88
|
+
|
89
|
+
# Attributi per l'elemento input standalone
|
90
|
+
def input_attributes
|
91
|
+
{
|
92
|
+
type: @type,
|
93
|
+
name: @name,
|
94
|
+
id: @name,
|
95
|
+
value: @value,
|
96
|
+
required: @required,
|
97
|
+
disabled: @disabled,
|
98
|
+
min: @min,
|
99
|
+
max: @max,
|
100
|
+
class: build_classes
|
101
|
+
}.compact.merge(@options)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Attributi per l'elemento input con form builder
|
105
|
+
def form_input_attributes
|
106
|
+
{
|
107
|
+
class: build_classes,
|
108
|
+
required: @required,
|
109
|
+
disabled: @disabled,
|
110
|
+
min: @min,
|
111
|
+
max: @max
|
112
|
+
}.compact.merge(@options)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Metodo helper per Rails form builder specifico per tipo
|
116
|
+
def form_field_method
|
117
|
+
case @type
|
118
|
+
when :date then :date_field
|
119
|
+
when :month then :month_field
|
120
|
+
when :week then :week_field
|
121
|
+
when :time then :time_field
|
122
|
+
else :date_field
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# Costruisce le classi CSS complete
|
129
|
+
def build_classes
|
130
|
+
[
|
131
|
+
DATETIME_INPUT_BASE_CLASSES,
|
132
|
+
get_theme_classes,
|
133
|
+
get_size_classes,
|
134
|
+
get_rounded_classes,
|
135
|
+
@classes
|
136
|
+
].compact.join(' ')
|
137
|
+
end
|
138
|
+
|
139
|
+
# Restituisce le classi del tema
|
140
|
+
def get_theme_classes
|
141
|
+
DATETIME_INPUT_THEME[@theme]
|
142
|
+
end
|
143
|
+
|
144
|
+
# Restituisce le classi della dimensione
|
145
|
+
def get_size_classes
|
146
|
+
DATETIME_INPUT_SIZES[@size]
|
147
|
+
end
|
148
|
+
|
149
|
+
# Restituisce le classi del border radius
|
150
|
+
def get_rounded_classes
|
151
|
+
DATETIME_INPUT_RADIUS[@rounded]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Valida i parametri del componente
|
155
|
+
def validate_params
|
156
|
+
validate_type
|
157
|
+
validate_theme
|
158
|
+
validate_size
|
159
|
+
validate_rounded
|
160
|
+
validate_datetime_format
|
161
|
+
end
|
162
|
+
|
163
|
+
# Valida il tipo datetime
|
164
|
+
def validate_type
|
165
|
+
return if DATETIME_INPUT_TYPES.include?(@type)
|
166
|
+
|
167
|
+
raise ArgumentError, "Tipo non valido: #{@type}. Tipi supportati: #{DATETIME_INPUT_TYPES.join(', ')}"
|
168
|
+
end
|
169
|
+
|
170
|
+
# Valida il tema
|
171
|
+
def validate_theme
|
172
|
+
return if DATETIME_INPUT_THEME.key?(@theme)
|
173
|
+
|
174
|
+
raise ArgumentError, "Tema non valido: #{@theme}. Temi supportati: #{DATETIME_INPUT_THEME.keys.join(', ')}"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Valida la dimensione
|
178
|
+
def validate_size
|
179
|
+
return if DATETIME_INPUT_SIZES.key?(@size)
|
180
|
+
|
181
|
+
raise ArgumentError, "Dimensione non valida: #{@size}. Dimensioni supportate: #{DATETIME_INPUT_SIZES.keys.join(', ')}"
|
182
|
+
end
|
183
|
+
|
184
|
+
# Valida il border radius
|
185
|
+
def validate_rounded
|
186
|
+
return if DATETIME_INPUT_RADIUS.key?(@rounded)
|
187
|
+
|
188
|
+
raise ArgumentError, "Border radius non valido: #{@rounded}. Valori supportati: #{DATETIME_INPUT_RADIUS.keys.join(', ')}"
|
189
|
+
end
|
190
|
+
|
191
|
+
# Valida il formato dei valori datetime
|
192
|
+
def validate_datetime_format
|
193
|
+
validate_single_datetime(@value, 'value') if @value
|
194
|
+
validate_single_datetime(@min, 'min') if @min
|
195
|
+
validate_single_datetime(@max, 'max') if @max
|
196
|
+
end
|
197
|
+
|
198
|
+
# Valida un singolo valore datetime
|
199
|
+
def validate_single_datetime(datetime_string, field_name)
|
200
|
+
return if datetime_string.nil? || datetime_string.to_s.strip.empty?
|
201
|
+
|
202
|
+
pattern = DATETIME_FORMAT_PATTERNS[@type]
|
203
|
+
return if pattern && datetime_string.match?(pattern)
|
204
|
+
|
205
|
+
expected_format = get_expected_format(@type)
|
206
|
+
raise ArgumentError, "Il campo #{field_name} deve essere nel formato #{expected_format} per il tipo #{@type}: #{datetime_string}"
|
207
|
+
end
|
208
|
+
|
209
|
+
# Restituisce il formato atteso per il tipo
|
210
|
+
def get_expected_format(type)
|
211
|
+
case type
|
212
|
+
when :date then 'YYYY-MM-DD'
|
213
|
+
when :month then 'YYYY-MM'
|
214
|
+
when :week then 'YYYY-WXX'
|
215
|
+
when :time then 'HH:MM'
|
216
|
+
else 'formato valido'
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,230 @@
|
|
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-blue-600 focus:border-blue-500 focus:ring-blue-500 checked:bg-blue-600 checked:border-blue-600',
|
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-4 w-4',
|
23
|
+
medium: 'h-5 w-5',
|
24
|
+
large: 'h-6 w-6'
|
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-2',
|
39
|
+
medium: 'gap-2.5',
|
40
|
+
large: 'gap-3'
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
RADIO_LABEL_TEXT = {
|
44
|
+
small: 'text-sm',
|
45
|
+
medium: 'text-base',
|
46
|
+
large: 'text-lg'
|
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
|