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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +225 -119
  4. data/app/assets/stylesheets/better_ui/application.css +0 -356
  5. data/app/components/better_ui/application/card/component.html.erb +20 -0
  6. data/app/components/better_ui/application/card/component.rb +214 -0
  7. data/app/components/better_ui/application/main/component.html.erb +9 -0
  8. data/app/components/better_ui/application/main/component.rb +123 -0
  9. data/app/components/better_ui/application/navbar/component.html.erb +92 -0
  10. data/app/components/better_ui/application/navbar/component.rb +136 -0
  11. data/app/components/better_ui/application/sidebar/component.html.erb +190 -0
  12. data/app/components/better_ui/application/sidebar/component.rb +129 -0
  13. data/app/components/better_ui/general/alert/component.html.erb +32 -0
  14. data/app/components/better_ui/general/alert/component.rb +242 -0
  15. data/app/components/better_ui/general/avatar/component.html.erb +20 -0
  16. data/app/components/better_ui/general/avatar/component.rb +301 -0
  17. data/app/components/better_ui/general/badge/component.html.erb +23 -0
  18. data/app/components/better_ui/general/badge/component.rb +248 -0
  19. data/app/components/better_ui/general/breadcrumb/component.html.erb +15 -0
  20. data/app/components/better_ui/general/breadcrumb/component.rb +187 -0
  21. data/app/components/better_ui/general/button/component.html.erb +34 -0
  22. data/app/components/better_ui/general/button/component.rb +214 -0
  23. data/app/components/better_ui/general/divider/component.html.erb +10 -0
  24. data/app/components/better_ui/general/divider/component.rb +226 -0
  25. data/app/components/better_ui/general/dropdown/component.html.erb +14 -0
  26. data/app/components/better_ui/general/dropdown/component.rb +219 -0
  27. data/app/components/better_ui/general/dropdown/divider_component.html.erb +1 -0
  28. data/app/components/better_ui/general/dropdown/divider_component.rb +41 -0
  29. data/app/components/better_ui/general/dropdown/item_component.html.erb +6 -0
  30. data/app/components/better_ui/general/dropdown/item_component.rb +118 -0
  31. data/app/components/better_ui/general/field/component.html.erb +27 -0
  32. data/app/components/better_ui/general/field/component.rb +37 -0
  33. data/app/components/better_ui/general/heading/component.html.erb +22 -0
  34. data/app/components/better_ui/general/heading/component.rb +257 -0
  35. data/app/components/better_ui/general/icon/component.html.erb +7 -0
  36. data/app/components/better_ui/general/icon/component.rb +239 -0
  37. data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
  38. data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
  39. data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
  40. data/app/components/better_ui/general/input/datetime/component.rb +223 -0
  41. data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
  42. data/app/components/better_ui/general/input/radio/component.rb +230 -0
  43. data/app/components/better_ui/general/input/select/component.html.erb +16 -0
  44. data/app/components/better_ui/general/input/select/component.rb +184 -0
  45. data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
  46. data/app/components/better_ui/general/input/select/select_component.rb +37 -0
  47. data/app/components/better_ui/general/input/text/component.html.erb +5 -0
  48. data/app/components/better_ui/general/input/text/component.rb +171 -0
  49. data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
  50. data/app/components/better_ui/general/input/textarea/component.rb +166 -0
  51. data/app/components/better_ui/general/link/component.html.erb +18 -0
  52. data/app/components/better_ui/general/link/component.rb +258 -0
  53. data/app/components/better_ui/general/modal/component.html.erb +42 -0
  54. data/app/components/better_ui/general/modal/component.rb +165 -0
  55. data/app/components/better_ui/general/pagination/component.html.erb +85 -0
  56. data/app/components/better_ui/general/pagination/component.rb +216 -0
  57. data/app/components/better_ui/general/panel/component.html.erb +28 -0
  58. data/app/components/better_ui/general/panel/component.rb +249 -0
  59. data/app/components/better_ui/general/progress/component.html.erb +11 -0
  60. data/app/components/better_ui/general/progress/component.rb +160 -0
  61. data/app/components/better_ui/general/spinner/component.html.erb +35 -0
  62. data/app/components/better_ui/general/spinner/component.rb +93 -0
  63. data/app/components/better_ui/general/table/component.html.erb +5 -0
  64. data/app/components/better_ui/general/table/component.rb +217 -0
  65. data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
  66. data/app/components/better_ui/general/table/tbody_component.rb +30 -0
  67. data/app/components/better_ui/general/table/td_component.html.erb +3 -0
  68. data/app/components/better_ui/general/table/td_component.rb +44 -0
  69. data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
  70. data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
  71. data/app/components/better_ui/general/table/th_component.html.erb +6 -0
  72. data/app/components/better_ui/general/table/th_component.rb +51 -0
  73. data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
  74. data/app/components/better_ui/general/table/thead_component.rb +28 -0
  75. data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
  76. data/app/components/better_ui/general/table/tr_component.rb +30 -0
  77. data/app/components/better_ui/general/tabs/component.html.erb +3 -0
  78. data/app/components/better_ui/general/tabs/component.rb +102 -0
  79. data/app/components/better_ui/general/tabs/panel_component.html.erb +3 -0
  80. data/app/components/better_ui/general/tabs/panel_component.rb +37 -0
  81. data/app/components/better_ui/general/tabs/tab_component.html.erb +13 -0
  82. data/app/components/better_ui/general/tabs/tab_component.rb +111 -0
  83. data/app/components/better_ui/general/tag/component.html.erb +3 -0
  84. data/app/components/better_ui/general/tag/component.rb +104 -0
  85. data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
  86. data/app/components/better_ui/general/tooltip/component.rb +239 -0
  87. data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
  88. data/app/helpers/better_ui/application/components/card.rb +11 -0
  89. data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
  90. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
  91. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
  92. data/app/helpers/better_ui/application_helper.rb +51 -179
  93. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
  94. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
  95. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
  96. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
  97. data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
  98. data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
  99. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
  100. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +32 -0
  101. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +79 -0
  102. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +62 -0
  103. data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
  104. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
  105. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
  106. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
  107. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
  108. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
  109. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
  110. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
  111. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
  112. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
  113. data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
  114. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +95 -0
  115. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +82 -0
  116. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
  117. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
  118. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
  119. data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
  120. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
  121. data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
  122. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
  123. data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
  124. data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
  125. data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
  126. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +62 -0
  127. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +55 -0
  128. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +62 -0
  129. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
  130. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
  131. data/app/views/layouts/better_ui/application.html.erb +6 -124
  132. data/config/initializers/lookbook.rb +23 -0
  133. data/config/routes.rb +0 -8
  134. data/lib/better_ui/engine.rb +5 -19
  135. data/lib/better_ui/railtie.rb +20 -0
  136. data/lib/better_ui/version.rb +1 -1
  137. data/lib/better_ui.rb +4 -20
  138. metadata +155 -28
  139. data/app/controllers/better_ui/docs_controller.rb +0 -41
  140. data/app/views/better_ui/docs/component.html.erb +0 -365
  141. data/app/views/better_ui/docs/index.html.erb +0 -100
  142. data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -0,0 +1,16 @@
1
+ <select <%= tag.attributes(select_attributes).html_safe %>>
2
+ <% if @placeholder.present? && !@multiple %>
3
+ <option value="" disabled <%= "selected" if @selected.nil? %>><%= @placeholder %></option>
4
+ <% end %>
5
+ <% @options.each do |option| %>
6
+ <option
7
+ value="<%= option[:value] %>"
8
+ <%= "selected" if selected?(option) %>
9
+ <%= "disabled" if option[:disabled] %>
10
+ <%= option[:html]&.map { |k, v| "#{k}=\"#{v}\"" }&.join(' ').to_s.html_safe %>
11
+ >
12
+ <%= option[:label] %>
13
+ </option>
14
+ <% end %>
15
+ <%= content %>
16
+ </select>
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Input
6
+ module Select
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
16
+ 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"
26
+ }.freeze
27
+
28
+ # Dimensioni con classi Tailwind
29
+ 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"
42
+ }.freeze
43
+
44
+ # Stati del select
45
+ SELECT_STATE = {
46
+ disabled: "bg-gray-100 cursor-not-allowed opacity-75"
47
+ }.freeze
48
+
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)
57
+ # @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
63
+ def initialize(
64
+ name:,
65
+ options:,
66
+ selected: nil,
67
+ required: false,
68
+ disabled: false,
69
+ multiple: false,
70
+ theme: :default,
71
+ size: :medium,
72
+ rounded: :medium,
73
+ placeholder: nil,
74
+ form: nil,
75
+ options_html: {},
76
+ classes: '',
77
+ **html_options
78
+ )
79
+ @name = name
80
+ @options = options
81
+ @selected = selected
82
+ @required = required
83
+ @disabled = disabled
84
+ @multiple = multiple
85
+ @theme = theme.to_sym
86
+ @size = size.to_sym
87
+ @rounded = rounded.to_sym
88
+ @placeholder = placeholder
89
+ @form = form
90
+ @options_html = options_html
91
+ @classes = classes
92
+ @html_options = html_options
93
+
94
+ validate_params
95
+ end
96
+
97
+ def combined_classes
98
+ [
99
+ SELECT_BASE_CLASSES,
100
+ SELECT_THEME[@theme],
101
+ SELECT_SIZE[@size],
102
+ SELECT_ROUNDED[@rounded],
103
+ @disabled ? SELECT_STATE[:disabled] : nil,
104
+ @classes
105
+ ].compact.join(' ')
106
+ end
107
+
108
+ def select_attributes
109
+ attrs = {
110
+ name: form_field_name,
111
+ id: @html_options[:id] || form_field_id,
112
+ class: combined_classes
113
+ }
114
+
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
119
+
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
127
+ end
128
+
129
+ def form_field_name
130
+ if @form.present?
131
+ @form.field_name(@name)
132
+ else
133
+ @name
134
+ end
135
+ end
136
+
137
+ def form_field_id
138
+ if @form.present?
139
+ @form.field_id(@name)
140
+ else
141
+ @name.to_s.gsub(/[\[\]]+/, '_').gsub(/_$/, '')
142
+ end
143
+ end
144
+
145
+ def selected?(option)
146
+ return false if @selected.nil?
147
+
148
+ if @selected.is_a?(Array)
149
+ @selected.include?(option[:value])
150
+ else
151
+ option[:value].to_s == @selected.to_s
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def validate_params
158
+ validate_theme
159
+ validate_size
160
+ validate_rounded
161
+ 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
167
+ 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
173
+ 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
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,5 @@
1
+ <select name="<%= name %>" class="<%= classes %>" <%= "required" if required %> <%= "disabled" if disabled %> <%= "multiple" if multiple %> <%= options_html.map { |key, value| "#{key}=\"#{value}\"" }.join(" ") %>>
2
+ <% options.each do |option| %>
3
+ <option value="<%= option[:value] %>" <%= "selected" if option[:value] == selected %>><%= option[:label] %></option>
4
+ <% end %>
5
+ </select>
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Input
6
+ module Select
7
+ class SelectComponent < ViewComponent::Base
8
+ attr_reader :name, :options, :selected, :required, :disabled, :multiple, :options_html, :classes, :html_options
9
+
10
+ BASE_SELECT_CLASSES = "h-10 px-3 py-2 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm disabled:bg-gray-100 disabled:cursor-not-allowed"
11
+ # @param name [String] Nome del campo
12
+ # @param options [Array] Opzioni del select ([{value => label}, {value => label}, ...])
13
+ # @param selected [String, Array] Valore selezionato
14
+ # @param required [Boolean] Se il campo è obbligatorio
15
+ # @param disabled [Boolean] Se il campo è disabilitato
16
+ # @param multiple [Boolean] Se il select è multiplo
17
+ # @param options_html [Hash] Opzioni HTML aggiuntive per le option
18
+ # @param classes [String] Classi del campo
19
+ # @param html_options [Hash] Opzioni HTML del campo
20
+ def initialize(name:, options:, selected: nil, required: false, disabled: false, multiple: false, options_html: {}, classes: '', **html_options)
21
+ @name = name
22
+ @type = :select
23
+ @required = required
24
+ @disabled = disabled
25
+ @options = options.map { |option| { value: option[:value], label: option[:label] } }
26
+ @selected = selected
27
+ @multiple = multiple
28
+ @options_html = options_html
29
+ @classes = BASE_SELECT_CLASSES + classes
30
+
31
+ puts "Optionssssssssssssssssssssssss: #{selected}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ <% if @form %>
2
+ <%= @form.text_field(@name, **form_input_attributes) %>
3
+ <% else %>
4
+ <%= tag.input(**input_attributes) %>
5
+ <% end %>
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Input
6
+ module Text
7
+ class Component < ViewComponent::Base
8
+ attr_reader :name, :value, :placeholder, :required, :disabled, :classes, :options,
9
+ :theme, :size, :rounded, :form, :type
10
+
11
+ # Temi supportati per il Text Input
12
+ TEXT_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 Text Input
25
+ TEXT_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 Text Input
32
+ TEXT_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 Text Input
41
+ TEXT_INPUT_TYPES = [
42
+ :text, :password, :email, :tel, :url, :number, :search,
43
+ :date, :time, :datetime_local, :month, :week, :color
44
+ ].freeze
45
+
46
+ # Classi base per il Text Input
47
+ TEXT_INPUT_BASE_CLASSES = 'block w-full border shadow-sm disabled:bg-gray-100 disabled:cursor-not-allowed focus:outline-none focus:ring-1'
48
+
49
+ # @param name [String] Nome del campo input
50
+ # @param value [String] Valore del campo
51
+ # @param placeholder [String] Placeholder del campo
52
+ # @param required [Boolean] Se il campo è obbligatorio
53
+ # @param disabled [Boolean] Se il campo è disabilitato
54
+ # @param type [Symbol] Tipo del campo input (:text, :password, :email, :tel, :url, :number, :search, :date, :time, :datetime_local, :month, :week, :color)
55
+ # @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
56
+ # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
57
+ # @param rounded [Symbol] Border radius (:none, :small, :medium, :large, :full)
58
+ # @param classes [String] Classi CSS aggiuntive
59
+ # @param form [ActionView::Helpers::FormBuilder] Form builder Rails opzionale
60
+ # @param options [Hash] Opzioni aggiuntive per l'input
61
+ def initialize(name:, value: nil, placeholder: nil, required: false, disabled: false,
62
+ type: :text, theme: :default, size: :medium, rounded: :medium, classes: '', form: nil, **options)
63
+ @name = name
64
+ @value = value
65
+ @placeholder = placeholder
66
+ @required = required
67
+ @disabled = disabled
68
+ @type = type
69
+ @theme = theme
70
+ @size = size
71
+ @rounded = rounded
72
+ @classes = classes
73
+ @form = form
74
+ @options = options
75
+
76
+ validate_params
77
+ super()
78
+ end
79
+
80
+ # Attributi per l'elemento input standalone
81
+ def input_attributes
82
+ {
83
+ type: @type,
84
+ name: @name,
85
+ id: @name,
86
+ value: @value,
87
+ placeholder: @placeholder,
88
+ required: @required,
89
+ disabled: @disabled,
90
+ class: build_classes
91
+ }.merge(@options)
92
+ end
93
+
94
+ # Attributi per l'elemento input con form builder
95
+ def form_input_attributes
96
+ {
97
+ class: build_classes,
98
+ placeholder: @placeholder,
99
+ required: @required,
100
+ disabled: @disabled
101
+ }.merge(@options)
102
+ end
103
+
104
+ private
105
+
106
+ # Costruisce le classi CSS complete
107
+ def build_classes
108
+ [
109
+ TEXT_INPUT_BASE_CLASSES,
110
+ get_theme_classes,
111
+ get_size_classes,
112
+ get_rounded_classes,
113
+ @classes
114
+ ].compact.join(' ')
115
+ end
116
+
117
+ # Restituisce le classi del tema
118
+ def get_theme_classes
119
+ TEXT_INPUT_THEME[@theme]
120
+ end
121
+
122
+ # Restituisce le classi della dimensione
123
+ def get_size_classes
124
+ TEXT_INPUT_SIZES[@size]
125
+ end
126
+
127
+ # Restituisce le classi del border radius
128
+ def get_rounded_classes
129
+ TEXT_INPUT_RADIUS[@rounded]
130
+ end
131
+
132
+ # Valida i parametri del componente
133
+ def validate_params
134
+ validate_type
135
+ validate_theme
136
+ validate_size
137
+ validate_rounded
138
+ end
139
+
140
+ # Valida il tema
141
+ def validate_theme
142
+ return if TEXT_INPUT_THEME.key?(@theme)
143
+
144
+ raise ArgumentError, "Tema non valido: #{@theme}. Temi supportati: #{TEXT_INPUT_THEME.keys.join(', ')}"
145
+ end
146
+
147
+ # Valida la dimensione
148
+ def validate_size
149
+ return if TEXT_INPUT_SIZES.key?(@size)
150
+
151
+ raise ArgumentError, "Dimensione non valida: #{@size}. Dimensioni supportate: #{TEXT_INPUT_SIZES.keys.join(', ')}"
152
+ end
153
+
154
+ # Valida il border radius
155
+ def validate_rounded
156
+ return if TEXT_INPUT_RADIUS.key?(@rounded)
157
+
158
+ raise ArgumentError, "Border radius non valido: #{@rounded}. Valori supportati: #{TEXT_INPUT_RADIUS.keys.join(', ')}"
159
+ end
160
+
161
+ # Valida il tipo
162
+ def validate_type
163
+ return if TEXT_INPUT_TYPES.include?(@type)
164
+
165
+ raise ArgumentError, "Tipo non valido: #{@type}. Tipi supportati: #{TEXT_INPUT_TYPES.join(', ')}"
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,5 @@
1
+ <% if @form %>
2
+ <%= @form.text_area(@name, **form_textarea_attributes) %>
3
+ <% else %>
4
+ <%= tag.textarea(**textarea_attributes) %>
5
+ <% end %>
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Input
6
+ module Textarea
7
+ class Component < ViewComponent::Base
8
+ attr_reader :name, :value, :placeholder, :required, :disabled, :classes, :options,
9
+ :theme, :size, :rounded, :form, :rows
10
+
11
+ # Temi supportati per il Textarea
12
+ TEXTAREA_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 Textarea
25
+ TEXTAREA_SIZES = {
26
+ small: 'px-2 py-1 text-xs',
27
+ medium: 'px-3 py-2 text-sm',
28
+ large: 'px-4 py-3 text-base'
29
+ }.freeze
30
+
31
+ # Border radius supportati per il Textarea
32
+ TEXTAREA_RADIUS = {
33
+ none: 'rounded-none',
34
+ small: 'rounded-sm',
35
+ medium: 'rounded-md',
36
+ large: 'rounded-lg',
37
+ full: 'rounded-2xl'
38
+ }.freeze
39
+
40
+ # Classi base per il Textarea
41
+ TEXTAREA_BASE_CLASSES = 'block w-full border shadow-sm disabled:bg-gray-100 disabled:cursor-not-allowed focus:outline-none focus:ring-1 resize-y'
42
+
43
+ # @param name [String] Nome del campo textarea
44
+ # @param value [String] Valore del campo
45
+ # @param placeholder [String] Placeholder del campo
46
+ # @param required [Boolean] Se il campo è obbligatorio
47
+ # @param disabled [Boolean] Se il campo è disabilitato
48
+ # @param rows [Integer] Numero di righe per la textarea
49
+ # @param theme [Symbol] Tema del componente (:default, :white, :red, :rose, :orange, :green, :blue, :yellow, :violet)
50
+ # @param size [Symbol] Dimensione del componente (:small, :medium, :large)
51
+ # @param rounded [Symbol] Border radius (:none, :small, :medium, :large, :full)
52
+ # @param classes [String] Classi CSS aggiuntive
53
+ # @param form [ActionView::Helpers::FormBuilder] Form builder Rails opzionale
54
+ # @param options [Hash] Opzioni aggiuntive per la textarea
55
+ def initialize(name:, value: nil, placeholder: nil, required: false, disabled: false,
56
+ rows: 3, theme: :default, size: :medium, rounded: :medium, classes: '', form: nil, **options)
57
+ @name = name
58
+ @value = value
59
+ @placeholder = placeholder
60
+ @required = required
61
+ @disabled = disabled
62
+ @rows = rows
63
+ @theme = theme
64
+ @size = size
65
+ @rounded = rounded
66
+ @classes = classes
67
+ @form = form
68
+ @options = options
69
+
70
+ validate_params
71
+ super()
72
+ end
73
+
74
+ # Attributi per l'elemento textarea standalone
75
+ def textarea_attributes
76
+ {
77
+ name: @name,
78
+ id: @name,
79
+ value: @value,
80
+ placeholder: @placeholder,
81
+ required: @required,
82
+ disabled: @disabled,
83
+ rows: @rows,
84
+ class: build_classes
85
+ }.merge(@options)
86
+ end
87
+
88
+ # Attributi per l'elemento textarea con form builder
89
+ def form_textarea_attributes
90
+ {
91
+ class: build_classes,
92
+ placeholder: @placeholder,
93
+ required: @required,
94
+ disabled: @disabled,
95
+ rows: @rows
96
+ }.merge(@options)
97
+ end
98
+
99
+ private
100
+
101
+ # Costruisce le classi CSS complete
102
+ def build_classes
103
+ [
104
+ TEXTAREA_BASE_CLASSES,
105
+ get_theme_classes,
106
+ get_size_classes,
107
+ get_rounded_classes,
108
+ @classes
109
+ ].compact.join(' ')
110
+ end
111
+
112
+ # Restituisce le classi del tema
113
+ def get_theme_classes
114
+ TEXTAREA_THEME[@theme]
115
+ end
116
+
117
+ # Restituisce le classi della dimensione
118
+ def get_size_classes
119
+ TEXTAREA_SIZES[@size]
120
+ end
121
+
122
+ # Restituisce le classi del border radius
123
+ def get_rounded_classes
124
+ TEXTAREA_RADIUS[@rounded]
125
+ end
126
+
127
+ # Valida i parametri del componente
128
+ def validate_params
129
+ validate_theme
130
+ validate_size
131
+ validate_rounded
132
+ validate_rows
133
+ end
134
+
135
+ # Valida il tema
136
+ def validate_theme
137
+ return if TEXTAREA_THEME.key?(@theme)
138
+
139
+ raise ArgumentError, "Tema non valido: #{@theme}. Temi supportati: #{TEXTAREA_THEME.keys.join(', ')}"
140
+ end
141
+
142
+ # Valida la dimensione
143
+ def validate_size
144
+ return if TEXTAREA_SIZES.key?(@size)
145
+
146
+ raise ArgumentError, "Dimensione non valida: #{@size}. Dimensioni supportate: #{TEXTAREA_SIZES.keys.join(', ')}"
147
+ end
148
+
149
+ # Valida il border radius
150
+ def validate_rounded
151
+ return if TEXTAREA_RADIUS.key?(@rounded)
152
+
153
+ raise ArgumentError, "Border radius non valido: #{@rounded}. Valori supportati: #{TEXTAREA_RADIUS.keys.join(', ')}"
154
+ end
155
+
156
+ # Valida il numero di righe
157
+ def validate_rows
158
+ return if @rows.is_a?(Integer) && @rows.positive?
159
+
160
+ raise ArgumentError, "Il numero di righe deve essere un intero positivo: #{@rows}"
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,18 @@
1
+ <%# Template per il link %>
2
+ <% if link? %>
3
+ <%= link_to @href, **element_attributes do %>
4
+ <% if show_icon? %>
5
+ <span class="<%= icon_classes %>"><%= render_icon %></span>
6
+ <% end %>
7
+ <span class="<%= text_classes %>"><%= @label %></span>
8
+ <%= content %>
9
+ <% end %>
10
+ <% else %>
11
+ <%= tag.span(**element_attributes) do %>
12
+ <% if show_icon? %>
13
+ <span class="<%= icon_classes %>"><%= render_icon %></span>
14
+ <% end %>
15
+ <span class="<%= text_classes %>"><%= @label %></span>
16
+ <%= content %>
17
+ <% end %>
18
+ <% end %>