better_ui 0.1.0 → 0.1.1

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 (118) 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/field/component.html.erb +27 -0
  26. data/app/components/better_ui/general/field/component.rb +37 -0
  27. data/app/components/better_ui/general/heading/component.html.erb +22 -0
  28. data/app/components/better_ui/general/heading/component.rb +257 -0
  29. data/app/components/better_ui/general/icon/component.html.erb +7 -0
  30. data/app/components/better_ui/general/icon/component.rb +239 -0
  31. data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
  32. data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
  33. data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
  34. data/app/components/better_ui/general/input/datetime/component.rb +223 -0
  35. data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
  36. data/app/components/better_ui/general/input/radio/component.rb +230 -0
  37. data/app/components/better_ui/general/input/select/component.html.erb +16 -0
  38. data/app/components/better_ui/general/input/select/component.rb +184 -0
  39. data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
  40. data/app/components/better_ui/general/input/select/select_component.rb +37 -0
  41. data/app/components/better_ui/general/input/text/component.html.erb +5 -0
  42. data/app/components/better_ui/general/input/text/component.rb +171 -0
  43. data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
  44. data/app/components/better_ui/general/input/textarea/component.rb +166 -0
  45. data/app/components/better_ui/general/link/component.html.erb +18 -0
  46. data/app/components/better_ui/general/link/component.rb +258 -0
  47. data/app/components/better_ui/general/panel/component.html.erb +28 -0
  48. data/app/components/better_ui/general/panel/component.rb +249 -0
  49. data/app/components/better_ui/general/progress/component.html.erb +11 -0
  50. data/app/components/better_ui/general/progress/component.rb +160 -0
  51. data/app/components/better_ui/general/spinner/component.html.erb +35 -0
  52. data/app/components/better_ui/general/spinner/component.rb +93 -0
  53. data/app/components/better_ui/general/table/component.html.erb +5 -0
  54. data/app/components/better_ui/general/table/component.rb +217 -0
  55. data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
  56. data/app/components/better_ui/general/table/tbody_component.rb +30 -0
  57. data/app/components/better_ui/general/table/td_component.html.erb +3 -0
  58. data/app/components/better_ui/general/table/td_component.rb +44 -0
  59. data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
  60. data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
  61. data/app/components/better_ui/general/table/th_component.html.erb +6 -0
  62. data/app/components/better_ui/general/table/th_component.rb +51 -0
  63. data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
  64. data/app/components/better_ui/general/table/thead_component.rb +28 -0
  65. data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
  66. data/app/components/better_ui/general/table/tr_component.rb +30 -0
  67. data/app/components/better_ui/general/tag/component.html.erb +3 -0
  68. data/app/components/better_ui/general/tag/component.rb +104 -0
  69. data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
  70. data/app/components/better_ui/general/tooltip/component.rb +239 -0
  71. data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
  72. data/app/helpers/better_ui/application/components/card.rb +11 -0
  73. data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
  74. data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
  75. data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
  76. data/app/helpers/better_ui/application_helper.rb +42 -179
  77. data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
  78. data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
  79. data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
  80. data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
  81. data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
  82. data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
  83. data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
  84. data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
  85. data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
  86. data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
  87. data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
  88. data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
  89. data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
  90. data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
  91. data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
  92. data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
  93. data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
  94. data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
  95. data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
  96. data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
  97. data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
  98. data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
  99. data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
  100. data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
  101. data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
  102. data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
  103. data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
  104. data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
  105. data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
  106. data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
  107. data/app/views/layouts/better_ui/application.html.erb +6 -124
  108. data/config/initializers/lookbook.rb +23 -0
  109. data/config/routes.rb +0 -8
  110. data/lib/better_ui/engine.rb +5 -19
  111. data/lib/better_ui/railtie.rb +20 -0
  112. data/lib/better_ui/version.rb +1 -1
  113. data/lib/better_ui.rb +4 -20
  114. metadata +131 -28
  115. data/app/controllers/better_ui/docs_controller.rb +0 -41
  116. data/app/views/better_ui/docs/component.html.erb +0 -365
  117. data/app/views/better_ui/docs/index.html.erb +0 -100
  118. data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -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 %>
@@ -0,0 +1,258 @@
1
+ module BetterUi
2
+ module General
3
+ module Link
4
+ class Component < ViewComponent::Base
5
+ attr_reader :label, :href, :theme, :orientation, :style, :size, :icon, :active, :disabled, :data, :method, :target
6
+
7
+ # Classi base sempre presenti
8
+ LINK_BASE_CLASSES = "transition-colors duration-200 no-underline"
9
+
10
+ # Temi con classi Tailwind dirette - LOGICA CORRETTA
11
+ LINK_THEME_CLASSES = {
12
+ default: "text-white hover:text-gray-300", # Bianco per sfondi scuri
13
+ white: "text-gray-900 hover:text-gray-700", # Nero per sfondi chiari
14
+ red: "text-red-500 hover:text-red-600",
15
+ rose: "text-rose-500 hover:text-rose-600",
16
+ orange: "text-orange-500 hover:text-orange-600",
17
+ green: "text-green-500 hover:text-green-600",
18
+ blue: "text-blue-500 hover:text-blue-600",
19
+ yellow: "text-yellow-600 hover:text-yellow-700",
20
+ violet: "text-violet-500 hover:text-violet-600"
21
+ }
22
+
23
+ # Orientamenti con classi Tailwind dirette
24
+ LINK_ORIENTATION_CLASSES = {
25
+ horizontal: "inline-flex items-center",
26
+ vertical: "flex flex-col items-center"
27
+ }
28
+
29
+ # Stili con classi Tailwind dirette
30
+ LINK_STYLE_CLASSES = {
31
+ default: "",
32
+ underline: "underline",
33
+ bold: "font-bold",
34
+ text: "no-underline"
35
+ }
36
+
37
+ # Dimensioni con classi Tailwind dirette
38
+ LINK_SIZE_CLASSES = {
39
+ extra_small: "text-[0.65rem]",
40
+ small: "text-sm",
41
+ medium: "text-base",
42
+ large: "text-lg"
43
+ }
44
+
45
+ # Stati con classi Tailwind dirette
46
+ LINK_STATE_CLASSES = {
47
+ normal: "",
48
+ active: "font-semibold",
49
+ disabled: "opacity-50 cursor-not-allowed pointer-events-none"
50
+ }
51
+
52
+ # @param label [String] testo del link
53
+ # @param href [String] URL di destinazione (nil per semplice testo)
54
+ # @param theme [Symbol] tema del colore (:default, :white, etc.)
55
+ # @param orientation [Symbol] orientamento (:horizontal, :vertical)
56
+ # @param style [Symbol] stile (:default, :underline, :bold, :text)
57
+ # @param size [Symbol] dimensione (:extra_small, :small, :medium, :large)
58
+ # @param icon [String] icona opzionale
59
+ # @param active [Boolean] stato attivo del link
60
+ # @param disabled [Boolean] stato disabilitato del link
61
+ # @param data [Hash] attributi data
62
+ # @param method [Symbol] metodo HTTP (per Turbo)
63
+ # @param target [String] target del link
64
+ # @param html_options [Hash] opzioni HTML aggiuntive
65
+ def initialize(
66
+ label:,
67
+ href: nil,
68
+ theme: :white,
69
+ orientation: :horizontal,
70
+ style: :default,
71
+ size: :medium,
72
+ icon: nil,
73
+ active: false,
74
+ disabled: false,
75
+ data: {},
76
+ method: nil,
77
+ target: nil,
78
+ **html_options
79
+ )
80
+ @label = label
81
+ @href = href
82
+ @theme = theme.to_sym
83
+ @orientation = orientation.to_sym
84
+ @style = style.to_sym
85
+ @size = size.to_sym
86
+ @icon = icon
87
+ @active = active
88
+ @disabled = disabled
89
+ @data = data || {}
90
+ @method = method
91
+ @target = target
92
+ @html_options = html_options
93
+
94
+ validate_params
95
+ end
96
+
97
+ # Determina se è un link attivo/corrente
98
+ def active?
99
+ @active
100
+ end
101
+
102
+ # Determina se è disabilitato
103
+ def disabled?
104
+ @disabled
105
+ end
106
+
107
+ # Determina se è un link o solo testo
108
+ def link?
109
+ @href.present? && !@disabled
110
+ end
111
+
112
+ # Combina tutte le classi CSS
113
+ def combined_classes
114
+ [
115
+ LINK_BASE_CLASSES,
116
+ get_theme_class,
117
+ get_orientation_class,
118
+ get_style_class,
119
+ get_size_class,
120
+ get_state_class,
121
+ @html_options[:class]
122
+ ].compact.join(" ")
123
+ end
124
+
125
+ # Classi per l'icona con dimensionamento proporzionale
126
+ def icon_classes
127
+ return "" unless @icon.present?
128
+
129
+ # Definisce spacing e dimensioni icona basate su size
130
+ base_spacing = case @orientation
131
+ when :horizontal
132
+ "mr-2"
133
+ when :vertical
134
+ "mb-1"
135
+ else
136
+ "mr-2"
137
+ end
138
+
139
+ icon_size = case @size
140
+ when :extra_small
141
+ "w-3 h-3"
142
+ when :small
143
+ "w-4 h-4"
144
+ when :medium
145
+ "w-5 h-5"
146
+ when :large
147
+ "w-6 h-6"
148
+ else
149
+ "w-5 h-5"
150
+ end
151
+
152
+ "#{base_spacing} #{icon_size} inline-block"
153
+ end
154
+
155
+ # Classi per il testo
156
+ def text_classes
157
+ "inline-block"
158
+ end
159
+
160
+ # Restituisce gli attributi per il link/span
161
+ def element_attributes
162
+ attrs = @html_options.except(:class)
163
+ attrs[:class] = combined_classes
164
+
165
+ if link?
166
+ # Attributi specifici per i link
167
+ if @method.present?
168
+ attrs[:data] = @data.merge(turbo_method: @method)
169
+ elsif @data.present?
170
+ attrs[:data] = @data
171
+ end
172
+
173
+ attrs[:target] = @target if @target.present?
174
+ attrs[:aria] ||= {}
175
+ attrs[:aria][:current] = "page" if active?
176
+ else
177
+ # Attributi per span (testo semplice o disabilitato)
178
+ attrs[:aria] ||= {}
179
+ attrs[:aria][:disabled] = true if disabled?
180
+ end
181
+
182
+ attrs
183
+ end
184
+
185
+ # Determina se mostrare l'icona
186
+ def show_icon?
187
+ @icon.present?
188
+ end
189
+
190
+ # Renderizza l'icona
191
+ def render_icon
192
+ return nil unless show_icon?
193
+
194
+ if @icon.is_a?(String)
195
+ render BetterUi::General::IconComponent.new(name: @icon)
196
+ else
197
+ @icon # Assumiamo che sia già un componente renderizzato
198
+ end
199
+ end
200
+
201
+ private
202
+
203
+ def get_theme_class
204
+ LINK_THEME_CLASSES[@theme] || LINK_THEME_CLASSES[:white]
205
+ end
206
+
207
+ def get_orientation_class
208
+ LINK_ORIENTATION_CLASSES[@orientation] || LINK_ORIENTATION_CLASSES[:horizontal]
209
+ end
210
+
211
+ def get_style_class
212
+ LINK_STYLE_CLASSES[@style] || LINK_STYLE_CLASSES[:default]
213
+ end
214
+
215
+ def get_size_class
216
+ LINK_SIZE_CLASSES[@size] || LINK_SIZE_CLASSES[:medium]
217
+ end
218
+
219
+ def get_state_class
220
+ return LINK_STATE_CLASSES[:disabled] if disabled?
221
+ return LINK_STATE_CLASSES[:active] if active?
222
+ LINK_STATE_CLASSES[:normal]
223
+ end
224
+
225
+ def validate_params
226
+ validate_theme
227
+ validate_orientation
228
+ validate_style
229
+ validate_size
230
+ end
231
+
232
+ def validate_theme
233
+ unless LINK_THEME_CLASSES.keys.include?(@theme)
234
+ raise ArgumentError, "Il tema deve essere uno tra: #{LINK_THEME_CLASSES.keys.join(', ')}"
235
+ end
236
+ end
237
+
238
+ def validate_orientation
239
+ unless LINK_ORIENTATION_CLASSES.keys.include?(@orientation)
240
+ raise ArgumentError, "L'orientamento deve essere uno tra: #{LINK_ORIENTATION_CLASSES.keys.join(', ')}"
241
+ end
242
+ end
243
+
244
+ def validate_style
245
+ unless LINK_STYLE_CLASSES.keys.include?(@style)
246
+ raise ArgumentError, "Lo stile deve essere uno tra: #{LINK_STYLE_CLASSES.keys.join(', ')}"
247
+ end
248
+ end
249
+
250
+ def validate_size
251
+ unless LINK_SIZE_CLASSES.keys.include?(@size)
252
+ raise ArgumentError, "La dimensione deve essere una tra: #{LINK_SIZE_CLASSES.keys.join(', ')}"
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,28 @@
1
+ <%# Template per il panel %>
2
+ <div <%= tag.attributes(panel_attributes) %>>
3
+ <% if show_header? %>
4
+ <div class="<%= header_classes %>">
5
+ <% if @header.present? %>
6
+ <%= raw @header %>
7
+ <% elsif @title.present? %>
8
+ <div class="<%= title_classes %>"><%= @title %></div>
9
+ <% end %>
10
+ </div>
11
+ <% end %>
12
+
13
+ <% if show_body? %>
14
+ <div class="<%= body_classes %>">
15
+ <% if @body.present? %>
16
+ <%= raw @body %>
17
+ <% elsif content.present? %>
18
+ <%= content %>
19
+ <% end %>
20
+ </div>
21
+ <% end %>
22
+
23
+ <% if show_footer? %>
24
+ <div class="<%= footer_classes %>">
25
+ <%= raw @footer %>
26
+ </div>
27
+ <% end %>
28
+ </div>