better_ui 0.1.1 → 0.3.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/better_ui/application/sidebar/component.html.erb +53 -16
  3. data/app/components/better_ui/application/sidebar/component.rb +3 -2
  4. data/app/components/better_ui/general/accordion/component.html.erb +5 -0
  5. data/app/components/better_ui/general/accordion/component.rb +92 -0
  6. data/app/components/better_ui/general/accordion/item_component.html.erb +12 -0
  7. data/app/components/better_ui/general/accordion/item_component.rb +176 -0
  8. data/app/components/better_ui/general/button/component.html.erb +4 -4
  9. data/app/components/better_ui/general/dropdown/component.html.erb +25 -0
  10. data/app/components/better_ui/general/dropdown/component.rb +170 -0
  11. data/app/components/better_ui/general/dropdown/divider_component.html.erb +1 -0
  12. data/app/components/better_ui/general/dropdown/divider_component.rb +41 -0
  13. data/app/components/better_ui/general/dropdown/item_component.html.erb +6 -0
  14. data/app/components/better_ui/general/dropdown/item_component.rb +119 -0
  15. data/app/components/better_ui/general/modal/component.html.erb +5 -0
  16. data/app/components/better_ui/general/modal/component.rb +47 -0
  17. data/app/components/better_ui/general/modal/modal_component.html.erb +52 -0
  18. data/app/components/better_ui/general/modal/modal_component.rb +160 -0
  19. data/app/components/better_ui/general/pagination/component.html.erb +85 -0
  20. data/app/components/better_ui/general/pagination/component.rb +216 -0
  21. data/app/components/better_ui/general/tabs/component.html.erb +11 -0
  22. data/app/components/better_ui/general/tabs/component.rb +120 -0
  23. data/app/components/better_ui/general/tabs/panel_component.html.erb +3 -0
  24. data/app/components/better_ui/general/tabs/panel_component.rb +37 -0
  25. data/app/components/better_ui/general/tabs/tab_component.html.erb +13 -0
  26. data/app/components/better_ui/general/tabs/tab_component.rb +111 -0
  27. data/app/helpers/better_ui/application_helper.rb +12 -3
  28. data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +73 -0
  29. data/app/helpers/better_ui/general/components/accordion.rb +11 -0
  30. data/app/helpers/better_ui/general/components/dropdown/divider_helper.rb +32 -0
  31. data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +79 -0
  32. data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +62 -0
  33. data/app/helpers/better_ui/general/components/modal/modal_helper.rb +85 -0
  34. data/app/helpers/better_ui/general/components/modal.rb +11 -0
  35. data/app/helpers/better_ui/general/components/pagination/pagination_helper.rb +82 -0
  36. data/app/helpers/better_ui/general/components/tabs/panel_helper.rb +62 -0
  37. data/app/helpers/better_ui/general/components/tabs/tab_helper.rb +55 -0
  38. data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +95 -0
  39. data/lib/better_ui/version.rb +1 -1
  40. metadata +35 -2
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Dropdown
6
+ class DividerComponent < ViewComponent::Base
7
+ attr_reader :classes, :html_options
8
+
9
+ # Classi base per il divisore del dropdown
10
+ DROPDOWN_DIVIDER_CLASSES = "border-t border-gray-100 my-1"
11
+
12
+ def initialize(classes: nil, **html_options)
13
+ @classes = classes
14
+ @html_options = html_options
15
+ end
16
+
17
+ # Combina tutte le classi per il divisore
18
+ def divider_classes
19
+ [
20
+ DROPDOWN_DIVIDER_CLASSES,
21
+ @classes
22
+ ].compact.join(" ")
23
+ end
24
+
25
+ # Restituisce gli attributi per il divisore
26
+ def divider_attributes
27
+ attrs = {
28
+ class: divider_classes,
29
+ role: "separator"
30
+ }
31
+
32
+ @html_options.except(:class).each do |key, value|
33
+ attrs[key] = value
34
+ end
35
+
36
+ attrs
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ <<%= tag_name %> <%= tag.attributes(item_attributes) %>>
2
+ <% if @icon.present? %>
3
+ <span class="mr-3 flex-shrink-0"><%= bui_icon(@icon, classes: "h-5 w-5") %></span>
4
+ <% end %>
5
+ <%= @text %>
6
+ </<%= tag_name %>>
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterUi
4
+ module General
5
+ module Dropdown
6
+ class ItemComponent < ViewComponent::Base
7
+ include BetterUi::General::Components::Icon::IconHelper
8
+
9
+ attr_reader :text, :icon, :href, :theme, :disabled, :active, :classes, :html_options
10
+
11
+ # Classi base per l'elemento del dropdown
12
+ DROPDOWN_ITEM_BASE_CLASSES = "flex items-center w-full px-4 py-2 text-sm transition-colors"
13
+
14
+ # Temi per gli elementi del dropdown con classi Tailwind dirette
15
+ DROPDOWN_ITEM_THEME = {
16
+ default: "text-gray-700 hover:bg-gray-100 hover:text-gray-900",
17
+ white: "text-gray-900 hover:bg-gray-50",
18
+ red: "text-red-700 hover:bg-red-50 hover:text-red-900",
19
+ rose: "text-rose-700 hover:bg-rose-50 hover:text-rose-900",
20
+ orange: "text-orange-700 hover:bg-orange-50 hover:text-orange-900",
21
+ green: "text-green-700 hover:bg-green-50 hover:text-green-900",
22
+ blue: "text-blue-700 hover:bg-blue-50 hover:text-blue-900",
23
+ yellow: "text-yellow-700 hover:bg-yellow-50 hover:text-yellow-900",
24
+ violet: "text-violet-700 hover:bg-violet-50 hover:text-violet-900"
25
+ }.freeze
26
+
27
+ # Stati per gli elementi del dropdown
28
+ DROPDOWN_ITEM_STATE_DISABLED = "opacity-50 cursor-not-allowed pointer-events-none"
29
+ DROPDOWN_ITEM_STATE_ACTIVE = "bg-gray-100 text-gray-900"
30
+
31
+ def initialize(
32
+ text:,
33
+ icon: nil,
34
+ href: nil,
35
+ theme: :default,
36
+ disabled: false,
37
+ active: false,
38
+ classes: nil,
39
+ **html_options
40
+ )
41
+ @text = text
42
+ @icon = icon
43
+ @href = href
44
+ @theme = theme.to_sym
45
+ @disabled = disabled
46
+ @active = active
47
+ @classes = classes
48
+ @html_options = html_options
49
+
50
+ validate_params
51
+ end
52
+
53
+ # Combina tutte le classi per l'elemento
54
+ def item_classes
55
+ classes = [
56
+ DROPDOWN_ITEM_BASE_CLASSES,
57
+ get_theme_classes,
58
+ @classes
59
+ ]
60
+
61
+ classes << DROPDOWN_ITEM_STATE_DISABLED if @disabled
62
+ classes << DROPDOWN_ITEM_STATE_ACTIVE if @active
63
+
64
+ classes.compact.join(" ")
65
+ end
66
+
67
+ # Restituisce gli attributi per l'elemento
68
+ def item_attributes
69
+ attrs = {
70
+ class: item_classes,
71
+ role: "menuitem",
72
+ "data-bui-dropdown-target": "item"
73
+ }
74
+
75
+ if @href.present? && !@disabled
76
+ attrs[:href] = @href
77
+ end
78
+
79
+ if @disabled
80
+ attrs["aria-disabled"] = "true"
81
+ attrs[:tabindex] = "-1"
82
+ end
83
+
84
+ @html_options.except(:class).each do |key, value|
85
+ attrs[key] = value
86
+ end
87
+
88
+ attrs
89
+ end
90
+
91
+ # Determina se usare un link o un button
92
+ def tag_name
93
+ @href.present? && !@disabled ? :a : :button
94
+ end
95
+
96
+ # Verifica se rendere il componente
97
+ def render?
98
+ @text.present?
99
+ end
100
+
101
+ private
102
+
103
+ def get_theme_classes
104
+ DROPDOWN_ITEM_THEME[@theme] || DROPDOWN_ITEM_THEME[:default]
105
+ end
106
+
107
+ def validate_params
108
+ validate_theme
109
+ end
110
+
111
+ def validate_theme
112
+ unless DROPDOWN_ITEM_THEME.keys.include?(@theme)
113
+ raise ArgumentError, "Il tema deve essere uno tra: #{DROPDOWN_ITEM_THEME.keys.join(', ')}"
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,5 @@
1
+ <%# Wrapper component con controller Stimulus e slots %>
2
+ <div <%= tag.attributes(wrapper_attributes) %>>
3
+ <%= trigger %>
4
+ <%= modal %>
5
+ </div>
@@ -0,0 +1,47 @@
1
+ module BetterUi
2
+ module General
3
+ module Modal
4
+ class Component < ViewComponent::Base
5
+ renders_one :trigger
6
+ renders_one :modal
7
+
8
+ attr_reader :close_on_backdrop, :close_on_escape, :lock_scroll, :classes, :html_options
9
+
10
+
11
+
12
+ # Inizializzazione del wrapper component
13
+ def initialize(
14
+ close_on_backdrop: true,
15
+ close_on_escape: true,
16
+ lock_scroll: true,
17
+ classes: nil,
18
+ **html_options
19
+ )
20
+ @close_on_backdrop = close_on_backdrop
21
+ @close_on_escape = close_on_escape
22
+ @lock_scroll = lock_scroll
23
+ @classes = classes
24
+ @html_options = html_options
25
+ end
26
+
27
+ # Combina tutte le classi per il wrapper
28
+ def wrapper_classes
29
+ [@classes, @html_options[:class]].compact.join(" ")
30
+ end
31
+
32
+ # Restituisce gli attributi per il wrapper principale (con controller Stimulus)
33
+ def wrapper_attributes
34
+ {
35
+ class: wrapper_classes,
36
+ 'data-controller': 'bui-modal',
37
+ 'data-bui-modal-close-on-backdrop-value': close_on_backdrop,
38
+ 'data-bui-modal-close-on-escape-value': close_on_escape,
39
+ 'data-bui-modal-lock-scroll-value': lock_scroll
40
+ }.merge(@html_options.except(:class))
41
+ end
42
+
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ <%# Template per il modal content %>
2
+ <% if @backdrop %>
3
+ <%= tag.div **backdrop_attributes do %>
4
+ <%= tag.div **container_attributes do %>
5
+ <%# Header del modal %>
6
+ <%= tag.div **header_attributes do %>
7
+ <h3 class="text-lg font-semibold" id="modal-title"><%= @title %></h3>
8
+ <% if @closable %>
9
+ <%= helpers.bui_button(
10
+ label: "Chiudi",
11
+ icon: "x-mark",
12
+ type: :white,
13
+ size: :small,
14
+ data: [
15
+ { name: 'bui-modal-target', value: 'closeButton' },
16
+ { name: 'action', value: 'click->bui-modal#closeButtonClicked' }
17
+ ]
18
+ ) %>
19
+ <% end %>
20
+ <% end %>
21
+
22
+ <%# Body del modal %>
23
+ <div class="p-6">
24
+ <%= content %>
25
+ </div>
26
+ <% end %>
27
+ <% end %>
28
+ <% else %>
29
+ <%= tag.div **container_attributes do %>
30
+ <%# Header del modal %>
31
+ <%= tag.div **header_attributes do %>
32
+ <h3 class="text-lg font-semibold" id="modal-title"><%= @title %></h3>
33
+ <% if @closable %>
34
+ <%= helpers.bui_button(
35
+ label: "Chiudi",
36
+ icon: "x-mark",
37
+ type: :white,
38
+ size: :small,
39
+ data: [
40
+ { name: 'bui-modal-target', value: 'closeButton' },
41
+ { name: 'action', value: 'click->bui-modal#closeButtonClicked' }
42
+ ]
43
+ ) %>
44
+ <% end %>
45
+ <% end %>
46
+
47
+ <%# Body del modal %>
48
+ <div class="p-6">
49
+ <%= content %>
50
+ </div>
51
+ <% end %>
52
+ <% end %>
@@ -0,0 +1,160 @@
1
+ module BetterUi
2
+ module General
3
+ module Modal
4
+ class ModalComponent < ViewComponent::Base
5
+ attr_reader :title, :theme, :size, :backdrop, :closable, :classes, :html_options
6
+
7
+ # Classi base sempre presenti per il backdrop
8
+ MODAL_BACKDROP_CLASSES = "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50"
9
+
10
+ # Classi base per il contenitore del modal
11
+ MODAL_CONTAINER_CLASSES = "relative bg-white shadow-xl w-full"
12
+
13
+ # Temi dell'header del modal con classi Tailwind dirette
14
+ MODAL_THEME = {
15
+ default: "bg-gray-50 border-b border-gray-200 text-gray-900",
16
+ white: "bg-white border-b border-gray-200 text-gray-900",
17
+ red: "bg-red-50 border-b border-red-200 text-red-900",
18
+ rose: "bg-rose-50 border-b border-rose-200 text-rose-900",
19
+ orange: "bg-orange-50 border-b border-orange-200 text-orange-900",
20
+ green: "bg-green-50 border-b border-green-200 text-green-900",
21
+ blue: "bg-blue-50 border-b border-blue-200 text-blue-900",
22
+ yellow: "bg-yellow-50 border-b border-yellow-200 text-yellow-900",
23
+ violet: "bg-violet-50 border-b border-violet-200 text-violet-900"
24
+ }
25
+
26
+ # Dimensioni con classi Tailwind dirette
27
+ MODAL_SIZES = {
28
+ small: "max-w-sm",
29
+ medium: "max-w-md",
30
+ large: "max-w-2xl"
31
+ }
32
+
33
+ # Border radius con classi Tailwind dirette
34
+ MODAL_ROUNDED = {
35
+ none: "rounded-none",
36
+ small: "rounded-md",
37
+ medium: "rounded-lg",
38
+ large: "rounded-xl",
39
+ full: "rounded-full"
40
+ }
41
+
42
+ # Inizializzazione del modal component
43
+ def initialize(
44
+ title:,
45
+ theme: :default,
46
+ size: :medium,
47
+ rounded: :medium,
48
+ backdrop: true,
49
+ closable: true,
50
+ classes: nil,
51
+ **html_options
52
+ )
53
+ @title = title
54
+ @theme = theme.to_sym
55
+ @size = size.to_sym
56
+ @rounded = rounded.to_sym
57
+ @backdrop = backdrop
58
+ @closable = closable
59
+ @classes = classes
60
+ @html_options = html_options
61
+
62
+ validate_params
63
+ end
64
+
65
+ # Combina tutte le classi per il backdrop
66
+ def backdrop_classes
67
+ MODAL_BACKDROP_CLASSES
68
+ end
69
+
70
+ # Combina tutte le classi per il contenitore
71
+ def container_classes
72
+ [
73
+ MODAL_CONTAINER_CLASSES,
74
+ get_modal_size_classes,
75
+ get_modal_rounded_classes,
76
+ @classes,
77
+ @html_options[:class]
78
+ ].compact.join(" ")
79
+ end
80
+
81
+ # Combina tutte le classi per l'header
82
+ def header_classes
83
+ [
84
+ "flex items-center justify-between p-6",
85
+ get_modal_theme_classes
86
+ ].compact.join(" ")
87
+ end
88
+
89
+ def get_modal_theme_classes
90
+ MODAL_THEME[@theme] || MODAL_THEME[:default]
91
+ end
92
+
93
+ def get_modal_size_classes
94
+ MODAL_SIZES[@size] || MODAL_SIZES[:medium]
95
+ end
96
+
97
+ def get_modal_rounded_classes
98
+ MODAL_ROUNDED[@rounded] || MODAL_ROUNDED[:medium]
99
+ end
100
+
101
+ # Restituisce gli attributi per il backdrop
102
+ def backdrop_attributes
103
+ {
104
+ class: backdrop_classes,
105
+ 'data-bui-modal-target': 'backdrop'
106
+ }
107
+ end
108
+
109
+ # Restituisce gli attributi per il contenitore
110
+ def container_attributes
111
+ {
112
+ class: container_classes,
113
+ role: "dialog",
114
+ "aria-modal": "true",
115
+ "aria-labelledby": "modal-title",
116
+ 'data-bui-modal-target': 'container'
117
+ }
118
+ end
119
+
120
+ # Restituisce gli attributi per l'header
121
+ def header_attributes
122
+ {
123
+ class: header_classes
124
+ }
125
+ end
126
+
127
+ # Verifica se rendere il componente
128
+ def render?
129
+ @title.present?
130
+ end
131
+
132
+ private
133
+
134
+ def validate_params
135
+ validate_theme
136
+ validate_size
137
+ validate_rounded
138
+ end
139
+
140
+ def validate_theme
141
+ unless MODAL_THEME.keys.include?(@theme)
142
+ raise ArgumentError, "Il tema deve essere uno tra: #{MODAL_THEME.keys.join(', ')}"
143
+ end
144
+ end
145
+
146
+ def validate_size
147
+ unless MODAL_SIZES.keys.include?(@size)
148
+ raise ArgumentError, "La dimensione deve essere una tra: #{MODAL_SIZES.keys.join(', ')}"
149
+ end
150
+ end
151
+
152
+ def validate_rounded
153
+ unless MODAL_ROUNDED.keys.include?(@rounded)
154
+ raise ArgumentError, "Il border radius deve essere uno tra: #{MODAL_ROUNDED.keys.join(', ')}"
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,85 @@
1
+ <% if should_show_pagination? %>
2
+ <nav aria-label="Pagination" class="flex items-center justify-between">
3
+
4
+ <!-- Info testo opzionale -->
5
+ <% if show_info %>
6
+ <div class="hidden sm:block">
7
+ <p class="text-sm text-gray-700"><%= info_text %></p>
8
+ </div>
9
+ <% end %>
10
+
11
+ <!-- Controlli paginazione -->
12
+ <div class="<%= container_classes %>">
13
+
14
+ <!-- Pulsante Previous -->
15
+ <% if previous_page %>
16
+ <a href="<%= build_url(previous_page) %>"
17
+ class="<%= page_link_classes(previous_page) %> rounded-l-md"
18
+ aria-label="Pagina precedente">
19
+ <span class="sr-only">Precedente</span>
20
+ <%= bui_icon(name: "chevron-left", size: :medium) %>
21
+ </a>
22
+ <% else %>
23
+ <span class="<%= disabled_classes %> rounded-l-md" aria-label="Pagina precedente">
24
+ <span class="sr-only">Precedente</span>
25
+ <%= bui_icon(name: "chevron-left", size: :medium) %>
26
+ </span>
27
+ <% end %>
28
+
29
+ <!-- Prima pagina se necessaria -->
30
+ <% if show_left_ellipsis? %>
31
+ <a href="<%= build_url(1) %>" class="<%= page_link_classes(1) %>">1</a>
32
+ <span class="<%= disabled_classes %>">...</span>
33
+ <% end %>
34
+
35
+ <!-- Range pagine -->
36
+ <% page_range.each do |page_num| %>
37
+ <% if page_num == current_page %>
38
+ <span aria-current="page" class="<%= page_link_classes(page_num) %>">
39
+ <%= page_num %>
40
+ </span>
41
+ <% else %>
42
+ <a href="<%= build_url(page_num) %>"
43
+ class="<%= page_link_classes(page_num) %>"
44
+ aria-label="Vai alla pagina <%= page_num %>">
45
+ <%= page_num %>
46
+ </a>
47
+ <% end %>
48
+ <% end %>
49
+
50
+ <!-- Ultima pagina se necessaria -->
51
+ <% if show_right_ellipsis? %>
52
+ <span class="<%= disabled_classes %>">...</span>
53
+ <a href="<%= build_url(total_pages) %>" class="<%= page_link_classes(total_pages) %>">
54
+ <%= total_pages %>
55
+ </a>
56
+ <% end %>
57
+
58
+ <!-- Pulsante Next -->
59
+ <% if next_page %>
60
+ <a href="<%= build_url(next_page) %>"
61
+ class="<%= page_link_classes(next_page) %> rounded-r-md"
62
+ aria-label="Pagina successiva">
63
+ <span class="sr-only">Successiva</span>
64
+ <%= bui_icon(name: "chevron-right", size: :medium) %>
65
+ </a>
66
+ <% else %>
67
+ <span class="<%= disabled_classes %> rounded-r-md" aria-label="Pagina successiva">
68
+ <span class="sr-only">Successiva</span>
69
+ <%= bui_icon(name: "chevron-right", size: :medium) %>
70
+ </span>
71
+ <% end %>
72
+
73
+ </div>
74
+
75
+ <!-- Info mobile -->
76
+ <% if show_info %>
77
+ <div class="flex flex-1 justify-between sm:hidden">
78
+ <span class="text-sm text-gray-700">
79
+ Pagina <%= current_page %> di <%= total_pages %>
80
+ </span>
81
+ </div>
82
+ <% end %>
83
+
84
+ </nav>
85
+ <% end %>