better_ui 0.2.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/components/better_ui/application/main/component.html.erb +1 -1
- data/app/components/better_ui/application/sidebar/component.html.erb +77 -18
- data/app/components/better_ui/application/sidebar/component.rb +63 -5
- data/app/components/better_ui/general/accordion/component.html.erb +5 -0
- data/app/components/better_ui/general/accordion/component.rb +92 -0
- data/app/components/better_ui/general/accordion/item_component.html.erb +12 -0
- data/app/components/better_ui/general/accordion/item_component.rb +176 -0
- data/app/components/better_ui/general/button/component.html.erb +8 -8
- data/app/components/better_ui/general/button/component.rb +11 -11
- data/app/components/better_ui/general/dropdown/component.html.erb +21 -7
- data/app/components/better_ui/general/dropdown/component.rb +27 -54
- data/app/components/better_ui/general/dropdown/item_component.rb +2 -1
- data/app/components/better_ui/general/field/component.html.erb +3 -3
- data/app/components/better_ui/general/field/component.rb +3 -3
- data/app/components/better_ui/general/grid/cell_component.html.erb +3 -0
- data/app/components/better_ui/general/grid/cell_component.rb +390 -0
- data/app/components/better_ui/general/grid/component.html.erb +3 -0
- data/app/components/better_ui/general/grid/component.rb +301 -0
- data/app/components/better_ui/general/heading/component.html.erb +1 -1
- data/app/components/better_ui/general/icon/component.rb +2 -1
- data/app/components/better_ui/general/input/checkbox/component.rb +10 -10
- data/app/components/better_ui/general/input/pin/component.html.erb +1 -0
- data/app/components/better_ui/general/input/pin/component.rb +201 -0
- data/app/components/better_ui/general/input/radio/component.rb +10 -10
- data/app/components/better_ui/general/input/rating/component.html.erb +4 -0
- data/app/components/better_ui/general/input/rating/component.rb +272 -0
- data/app/components/better_ui/general/input/select/component.html.erb +76 -14
- data/app/components/better_ui/general/input/select/component.rb +166 -101
- data/app/components/better_ui/general/input/toggle/component.html.erb +5 -0
- data/app/components/better_ui/general/input/toggle/component.rb +242 -0
- data/app/components/better_ui/general/link/component.rb +1 -1
- data/app/components/better_ui/general/modal/component.html.erb +5 -42
- data/app/components/better_ui/general/modal/component.rb +22 -140
- data/app/components/better_ui/general/modal/modal_component.html.erb +52 -0
- data/app/components/better_ui/general/modal/modal_component.rb +160 -0
- data/app/components/better_ui/general/tabs/component.html.erb +10 -2
- data/app/components/better_ui/general/tabs/component.rb +26 -8
- data/app/components/better_ui/general/tabs/panel_component.rb +1 -1
- data/app/components/better_ui/general/tabs/tab_component.rb +1 -1
- data/app/components/better_ui/general/text/component.html.erb +1 -0
- data/app/components/better_ui/general/text/component.rb +194 -0
- data/app/helpers/better_ui/application_helper.rb +11 -4
- data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +73 -0
- data/app/helpers/better_ui/general/components/button/button_helper.rb +6 -6
- data/app/helpers/better_ui/general/components/dropdown/dropdown_helper.rb +9 -0
- data/app/helpers/better_ui/general/components/dropdown/item_helper.rb +13 -7
- data/app/helpers/better_ui/general/components/field/field_helper.rb +4 -4
- data/app/helpers/better_ui/general/components/grid/grid_helper.rb +145 -0
- data/app/helpers/better_ui/general/components/input/pin/pin_helper.rb +76 -0
- data/app/helpers/better_ui/general/components/input/rating/rating_helper.rb +70 -0
- data/app/helpers/better_ui/general/components/input/select/select_helper.rb +47 -31
- data/app/helpers/better_ui/general/components/input/toggle/toggle_helper.rb +77 -0
- data/app/helpers/better_ui/general/components/modal/modal_helper.rb +34 -44
- data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +59 -26
- data/app/helpers/better_ui/general/components/text/text_helper.rb +83 -0
- data/lib/better_ui/version.rb +1 -1
- data/lib/better_ui.rb +1 -0
- metadata +26 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5ce58a483a9db3c7164c77c50c5206f83304c2b52f6ebff2d92dbc2ddfe0c78
|
4
|
+
data.tar.gz: 262e9f1b61ccf46254e582c2466cf4fea4c89d6ec48cb1117cb63355996a3e2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d024e430e9eb3d69c30adc09b8cff4a8a79a3585ea0769abe0e279c7dcb54b36faf7239ad393436f5558dc0db67c29a66e7eac8f32a11232d12cffbffc2cb96
|
7
|
+
data.tar.gz: 4c18bd0f1df604a289bab875eee931283616cd22f7b1a24e239446643392228a09b92f0e9b54888b9c3d8bdc2074a089ff0d35373aca0e60bccda86ef7b843fe
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<main class="w-full
|
1
|
+
<main class="w-full <%= get_layout_class %> <%= get_padding_class(padding) %><%= classes ? ' ' + classes : '' %>">
|
2
2
|
<div class="h-[calc(100vh-81px)] p-4">
|
3
3
|
<div class="h-full w-full bg-white p-4 <%= get_padding_class(inner_padding) %> <%= get_rounded_class %> <%= get_shadow_class %> overflow-y-auto">
|
4
4
|
<div class="h-full overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
|
@@ -1,10 +1,22 @@
|
|
1
|
-
<
|
1
|
+
<div data-controller="bui-sidebar"
|
2
|
+
data-bui-sidebar-width-value="<%= @width == :md ? 256 : 320 %>"
|
3
|
+
data-bui-sidebar-min-width-value="200"
|
4
|
+
data-bui-sidebar-max-width-value="400"
|
5
|
+
data-bui-sidebar-pinned-value="true"
|
6
|
+
class="<%= wrapper_classes %>">
|
7
|
+
|
8
|
+
<!-- Mobile Overlay -->
|
9
|
+
<div data-bui-sidebar-target="overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
|
10
|
+
|
11
|
+
<!-- Sidebar Container -->
|
12
|
+
<aside data-bui-sidebar-target="container" class="<%= container_classes %>">
|
2
13
|
<!-- Header Section -->
|
3
14
|
<% if has_header? %>
|
4
15
|
<div class="px-6 py-4 border-b border-gray-200">
|
5
|
-
|
6
|
-
|
7
|
-
<div class="flex-
|
16
|
+
<div class="flex items-center justify-between">
|
17
|
+
<% if header[:logo].present? %>
|
18
|
+
<div class="flex items-center">
|
19
|
+
<div class="flex-shrink-0">
|
8
20
|
<% if header[:logo].is_a?(Hash) %>
|
9
21
|
<%= bui_avatar(**header[:logo]) %>
|
10
22
|
<% else %>
|
@@ -20,19 +32,30 @@
|
|
20
32
|
</div>
|
21
33
|
<% end %>
|
22
34
|
</div>
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
<% elsif header[:title].present? %>
|
36
|
+
<div>
|
37
|
+
<h2 class="text-lg font-semibold text-gray-900"><%= header[:title] %></h2>
|
38
|
+
<% if header[:subtitle].present? %>
|
39
|
+
<p class="text-sm text-gray-500"><%= header[:subtitle] %></p>
|
40
|
+
<% end %>
|
41
|
+
</div>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<!-- Collapse Button (solo se collapsible) -->
|
45
|
+
<% if collapsible %>
|
46
|
+
<%= bui_button(
|
47
|
+
icon: "arrow-left",
|
48
|
+
type: :white,
|
49
|
+
size: :small,
|
50
|
+
title: "Comprimi sidebar"
|
51
|
+
) %>
|
52
|
+
<% end %>
|
53
|
+
</div>
|
31
54
|
</div>
|
32
55
|
<% end %>
|
33
56
|
|
34
57
|
<!-- Navigation Section -->
|
35
|
-
<nav class="flex-1 px-4 py-6 space-y-6">
|
58
|
+
<nav class="flex-1 px-4 py-6 space-y-6 overflow-y-auto">
|
36
59
|
<% navigation_sections.each do |section| %>
|
37
60
|
<div class="space-y-2">
|
38
61
|
<!-- Section Title -->
|
@@ -57,7 +80,9 @@
|
|
57
80
|
<button
|
58
81
|
type="button"
|
59
82
|
class="group flex items-center justify-between w-full px-3 py-2 text-sm font-medium text-gray-700 rounded-md hover:bg-gray-50 hover:text-gray-900 transition-colors duration-150"
|
60
|
-
data-
|
83
|
+
data-bui-sidebar-target="sectionTrigger"
|
84
|
+
data-bui-sidebar-section-id="<%= item[:id] %>"
|
85
|
+
data-action="click->bui-sidebar#toggleSection"
|
61
86
|
aria-expanded="<%= item[:expanded] || false %>"
|
62
87
|
>
|
63
88
|
<div class="flex items-center">
|
@@ -68,13 +93,14 @@
|
|
68
93
|
<% end %>
|
69
94
|
<span><%= item[:label] %></span>
|
70
95
|
</div>
|
71
|
-
<span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>">
|
96
|
+
<span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>" data-bui-sidebar-chevron>
|
72
97
|
<%= bui_icon("chevron-right", size: :small) %>
|
73
98
|
</span>
|
74
99
|
</button>
|
75
100
|
|
76
101
|
<div
|
77
|
-
|
102
|
+
data-bui-sidebar-target="sectionContent"
|
103
|
+
data-bui-sidebar-section-id="<%= item[:id] %>"
|
78
104
|
class="<%= item[:expanded] ? 'block' : 'hidden' %> mt-1 space-y-1"
|
79
105
|
>
|
80
106
|
<% (item[:children] || []).each do |child| %>
|
@@ -149,7 +175,28 @@
|
|
149
175
|
<!-- Footer Section -->
|
150
176
|
<% if has_footer? %>
|
151
177
|
<div class="px-6 py-4 border-t border-gray-200">
|
152
|
-
<% if
|
178
|
+
<% if has_user_dropdown? %>
|
179
|
+
<!-- User Dropdown -->
|
180
|
+
<% user_dropdown = footer[:user_dropdown] %>
|
181
|
+
<%= bui_dropdown(
|
182
|
+
trigger: user_dropdown_trigger,
|
183
|
+
position: :top,
|
184
|
+
theme: :white,
|
185
|
+
fullwidth: true,
|
186
|
+
show_chevron: false
|
187
|
+
) do %>
|
188
|
+
<% (user_dropdown[:menu_items] || []).each do |item| %>
|
189
|
+
<%= bui_dropdown_item(
|
190
|
+
text: item[:text],
|
191
|
+
icon: item[:icon],
|
192
|
+
href: item[:href],
|
193
|
+
theme: item[:theme] || :default,
|
194
|
+
active: item[:active] || false,
|
195
|
+
disabled: item[:disabled] || false
|
196
|
+
) %>
|
197
|
+
<% end %>
|
198
|
+
<% end %>
|
199
|
+
<% elsif footer[:user_info].present? %>
|
153
200
|
<div class="flex items-center">
|
154
201
|
<% if footer[:user_info][:avatar].present? %>
|
155
202
|
<div class="flex-shrink-0">
|
@@ -181,10 +228,22 @@
|
|
181
228
|
<% end %>
|
182
229
|
|
183
230
|
<% if footer[:content].present? %>
|
184
|
-
<div class="<%= footer[:user_info].present? ? 'mt-4' : '' %>">
|
231
|
+
<div class="<%= (footer[:user_info].present? || has_user_dropdown?) ? 'mt-4' : '' %>">
|
185
232
|
<%= footer[:content] %>
|
186
233
|
</div>
|
187
234
|
<% end %>
|
188
235
|
</div>
|
189
236
|
<% end %>
|
237
|
+
|
238
|
+
<!-- Resize Handle (solo se NON collapsible) -->
|
239
|
+
<% unless collapsible %>
|
240
|
+
<div data-bui-sidebar-target="resizeHandle"
|
241
|
+
class="absolute top-0 right-0 w-1 h-full bg-transparent hover:bg-blue-500 cursor-col-resize transition-colors duration-150 group">
|
242
|
+
<div class="absolute inset-y-0 -right-1 w-3 flex items-center justify-center">
|
243
|
+
<div class="w-0.5 h-8 bg-gray-300 group-hover:bg-blue-500 transition-colors duration-150"></div>
|
244
|
+
</div>
|
245
|
+
</div>
|
246
|
+
<% end %>
|
190
247
|
</aside>
|
248
|
+
|
249
|
+
</div>
|
@@ -4,9 +4,12 @@ module BetterUi
|
|
4
4
|
module Application
|
5
5
|
module Sidebar
|
6
6
|
class Component < ViewComponent::Base
|
7
|
-
# Include degli helper per utilizzare bui_icon e
|
7
|
+
# Include degli helper per utilizzare bui_icon, bui_avatar, bui_button e bui_dropdown
|
8
8
|
include BetterUi::General::Components::Icon::IconHelper
|
9
9
|
include BetterUi::General::Components::Avatar::AvatarHelper
|
10
|
+
include BetterUi::General::Components::Button::ButtonHelper
|
11
|
+
include BetterUi::General::Components::Dropdown::DropdownHelper
|
12
|
+
include BetterUi::General::Components::Dropdown::ItemHelper
|
10
13
|
attr_reader :width, :position, :theme, :shadow, :border, :header, :footer, :navigation_sections, :collapsible, :classes
|
11
14
|
|
12
15
|
# Larghezze sidebar con classi Tailwind dirette
|
@@ -45,7 +48,7 @@ module BetterUi
|
|
45
48
|
# @param shadow [Symbol] Tipo di ombra (:none, :sm, :md, :lg), default :lg
|
46
49
|
# @param border [Boolean] Se mostrare il bordo destro/sinistro, default true
|
47
50
|
# @param header [Hash] Configurazione header (logo, title, subtitle)
|
48
|
-
# @param footer [Hash] Configurazione footer (content, user_info)
|
51
|
+
# @param footer [Hash] Configurazione footer (content, user_info, user_dropdown)
|
49
52
|
# @param navigation_sections [Array] Array di sezioni di navigazione
|
50
53
|
# @param collapsible [Boolean] Se abilitare sezioni collassabili, default true
|
51
54
|
# @param classes [String] Classi CSS aggiuntive
|
@@ -73,8 +76,8 @@ module BetterUi
|
|
73
76
|
@classes = classes
|
74
77
|
end
|
75
78
|
|
76
|
-
def
|
77
|
-
base_classes = %w[fixed inset-y-0
|
79
|
+
def wrapper_classes
|
80
|
+
base_classes = %w[fixed top-0 inset-y-0 h-screen z-[9999]]
|
78
81
|
|
79
82
|
# Posizione
|
80
83
|
base_classes << (position == :right ? "right-0" : "left-0")
|
@@ -82,6 +85,12 @@ module BetterUi
|
|
82
85
|
# Larghezza
|
83
86
|
base_classes << width_class
|
84
87
|
|
88
|
+
base_classes.compact.join(" ")
|
89
|
+
end
|
90
|
+
|
91
|
+
def container_classes
|
92
|
+
base_classes = %w[flex flex-col h-full]
|
93
|
+
|
85
94
|
# Tema
|
86
95
|
base_classes.concat(theme_classes)
|
87
96
|
|
@@ -103,7 +112,56 @@ module BetterUi
|
|
103
112
|
end
|
104
113
|
|
105
114
|
def has_footer?
|
106
|
-
footer.present? && (footer[:content].present? || footer[:user_info].present?)
|
115
|
+
footer.present? && (footer[:content].present? || footer[:user_info].present? || footer[:user_dropdown].present?)
|
116
|
+
end
|
117
|
+
|
118
|
+
def has_user_dropdown?
|
119
|
+
footer.present? && footer[:user_dropdown].present?
|
120
|
+
end
|
121
|
+
|
122
|
+
def user_dropdown_trigger
|
123
|
+
return '' unless has_user_dropdown?
|
124
|
+
|
125
|
+
user_dropdown = footer[:user_dropdown]
|
126
|
+
avatar_html = if user_dropdown[:avatar].present?
|
127
|
+
if user_dropdown[:avatar].is_a?(Hash)
|
128
|
+
bui_avatar(**user_dropdown[:avatar])
|
129
|
+
else
|
130
|
+
user_dropdown[:avatar].html_safe
|
131
|
+
end
|
132
|
+
else
|
133
|
+
''
|
134
|
+
end
|
135
|
+
|
136
|
+
content_tag(:div, class: "flex items-center w-full text-left") do
|
137
|
+
avatar_section = if user_dropdown[:avatar].present?
|
138
|
+
content_tag(:div, avatar_html, class: "flex-shrink-0")
|
139
|
+
else
|
140
|
+
''
|
141
|
+
end
|
142
|
+
|
143
|
+
text_section = content_tag(:div, class: user_dropdown[:avatar].present? ? 'ml-3 min-w-0 flex-1' : 'min-w-0 flex-1') do
|
144
|
+
name_part = if user_dropdown[:name].present?
|
145
|
+
content_tag(:p, user_dropdown[:name], class: "text-sm font-medium text-gray-700 truncate")
|
146
|
+
else
|
147
|
+
''
|
148
|
+
end
|
149
|
+
|
150
|
+
subtitle_part = if user_dropdown[:subtitle].present?
|
151
|
+
content_tag(:p, user_dropdown[:subtitle], class: "text-xs text-gray-500 truncate")
|
152
|
+
else
|
153
|
+
''
|
154
|
+
end
|
155
|
+
|
156
|
+
(name_part + subtitle_part).html_safe
|
157
|
+
end
|
158
|
+
|
159
|
+
chevron_section = content_tag(:div, class: "ml-auto flex-shrink-0") do
|
160
|
+
bui_icon("chevron-down", size: :small, classes: "text-gray-400")
|
161
|
+
end
|
162
|
+
|
163
|
+
(avatar_section + text_section + chevron_section).html_safe
|
164
|
+
end
|
107
165
|
end
|
108
166
|
|
109
167
|
private
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module General
|
5
|
+
module Accordion
|
6
|
+
class Component < ViewComponent::Base
|
7
|
+
renders_many :items, "BetterUi::General::Accordion::ItemComponent"
|
8
|
+
|
9
|
+
ACCORDION_THEME = {
|
10
|
+
default: 'border-gray-200',
|
11
|
+
white: 'border-gray-100 bg-white',
|
12
|
+
blue: 'border-blue-200',
|
13
|
+
red: 'border-red-200',
|
14
|
+
green: 'border-green-200',
|
15
|
+
yellow: 'border-yellow-200',
|
16
|
+
violet: 'border-violet-200',
|
17
|
+
orange: 'border-orange-200',
|
18
|
+
rose: 'border-rose-200'
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
ACCORDION_VARIANT = {
|
22
|
+
minimal: '',
|
23
|
+
bordered: 'border rounded-lg',
|
24
|
+
separated: 'space-y-2'
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
ACCORDION_SIZE = {
|
28
|
+
small: 'text-sm',
|
29
|
+
medium: 'text-base',
|
30
|
+
large: 'text-lg'
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def initialize(multiple: false, theme: :default, variant: :bordered, size: :medium,
|
34
|
+
classes: '', **options)
|
35
|
+
@multiple = multiple
|
36
|
+
@theme = theme
|
37
|
+
@variant = variant
|
38
|
+
@size = size
|
39
|
+
@classes = classes
|
40
|
+
@options = options
|
41
|
+
|
42
|
+
validate_params
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :multiple, :theme, :variant, :size, :classes, :options
|
48
|
+
|
49
|
+
def validate_params
|
50
|
+
validate_theme
|
51
|
+
validate_variant
|
52
|
+
validate_size
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_theme
|
56
|
+
return if ACCORDION_THEME.key?(theme)
|
57
|
+
|
58
|
+
raise ArgumentError, "Invalid theme: #{theme}. Must be one of #{ACCORDION_THEME.keys}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_variant
|
62
|
+
return if ACCORDION_VARIANT.key?(variant)
|
63
|
+
|
64
|
+
raise ArgumentError, "Invalid variant: #{variant}. Must be one of #{ACCORDION_VARIANT.keys}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_size
|
68
|
+
return if ACCORDION_SIZE.key?(size)
|
69
|
+
|
70
|
+
raise ArgumentError, "Invalid size: #{size}. Must be one of #{ACCORDION_SIZE.keys}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Attributi per il wrapper principale
|
74
|
+
def wrapper_attributes
|
75
|
+
base_classes = [
|
76
|
+
'bui-accordion',
|
77
|
+
ACCORDION_SIZE[size],
|
78
|
+
ACCORDION_VARIANT[variant],
|
79
|
+
ACCORDION_THEME[theme],
|
80
|
+
classes
|
81
|
+
].compact.join(' ')
|
82
|
+
|
83
|
+
{
|
84
|
+
class: base_classes,
|
85
|
+
'data-controller': 'bui-accordion',
|
86
|
+
'data-bui-accordion-multiple-value': multiple
|
87
|
+
}.merge(options)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<div <%= tag.attributes(item_attributes) %>>
|
2
|
+
<!-- Header -->
|
3
|
+
<button <%= tag.attributes(header_attributes) %>>
|
4
|
+
<span><%= title %></span>
|
5
|
+
<i class="fas fa-<%= icon %> <%= icon_attributes[:class] %>" <%= tag.attributes(icon_attributes.except(:class)) %>></i>
|
6
|
+
</button>
|
7
|
+
|
8
|
+
<!-- Content -->
|
9
|
+
<div <%= tag.attributes(content_attributes) %>>
|
10
|
+
<%= content %>
|
11
|
+
</div>
|
12
|
+
</div>
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module General
|
5
|
+
module Accordion
|
6
|
+
class ItemComponent < ViewComponent::Base
|
7
|
+
ITEM_THEME = {
|
8
|
+
default: 'border-gray-200 bg-white',
|
9
|
+
white: 'border-gray-100 bg-white',
|
10
|
+
blue: 'border-blue-200 bg-blue-50',
|
11
|
+
red: 'border-red-200 bg-red-50',
|
12
|
+
green: 'border-green-200 bg-green-50',
|
13
|
+
yellow: 'border-yellow-200 bg-yellow-50',
|
14
|
+
violet: 'border-violet-200 bg-violet-50',
|
15
|
+
orange: 'border-orange-200 bg-orange-50',
|
16
|
+
rose: 'border-rose-200 bg-rose-50'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
ITEM_HEADER_THEME = {
|
20
|
+
default: 'text-gray-900 hover:bg-gray-50',
|
21
|
+
white: 'text-gray-900 hover:bg-gray-50',
|
22
|
+
blue: 'text-blue-900 hover:bg-blue-100',
|
23
|
+
red: 'text-red-900 hover:bg-red-100',
|
24
|
+
green: 'text-green-900 hover:bg-green-100',
|
25
|
+
yellow: 'text-yellow-900 hover:bg-yellow-100',
|
26
|
+
violet: 'text-violet-900 hover:bg-violet-100',
|
27
|
+
orange: 'text-orange-900 hover:bg-orange-100',
|
28
|
+
rose: 'text-rose-900 hover:bg-rose-100'
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
ITEM_SIZE_HEADER = {
|
32
|
+
small: 'text-sm px-4 py-3',
|
33
|
+
medium: 'text-base px-5 py-4',
|
34
|
+
large: 'text-lg px-6 py-5'
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
ITEM_SIZE_CONTENT = {
|
38
|
+
small: 'px-4 py-3',
|
39
|
+
medium: 'px-5 py-4',
|
40
|
+
large: 'px-6 py-5'
|
41
|
+
}.freeze
|
42
|
+
|
43
|
+
ITEM_SIZE_ICON = {
|
44
|
+
small: 'w-4 h-4',
|
45
|
+
medium: 'w-5 h-5',
|
46
|
+
large: 'w-6 h-6'
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
def initialize(title:, expanded: false, disabled: false, icon: 'chevron-down',
|
50
|
+
theme: :default, size: :medium, classes: '', **options)
|
51
|
+
@title = title
|
52
|
+
@expanded = expanded
|
53
|
+
@disabled = disabled
|
54
|
+
@icon = icon
|
55
|
+
@theme = theme
|
56
|
+
@size = size
|
57
|
+
@classes = classes
|
58
|
+
@options = options
|
59
|
+
|
60
|
+
validate_params
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_reader :title, :expanded, :disabled, :icon, :theme, :size, :classes, :options
|
66
|
+
|
67
|
+
def validate_params
|
68
|
+
validate_theme
|
69
|
+
validate_size
|
70
|
+
|
71
|
+
raise ArgumentError, 'title cannot be blank' if title.blank?
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_theme
|
75
|
+
return if ITEM_THEME.key?(theme)
|
76
|
+
|
77
|
+
raise ArgumentError, "Invalid theme: #{theme}. Must be one of #{ITEM_THEME.keys}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_size
|
81
|
+
return if ITEM_SIZE_HEADER.key?(size)
|
82
|
+
|
83
|
+
raise ArgumentError, "Invalid size: #{size}. Must be one of #{ITEM_SIZE_HEADER.keys}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def unique_id
|
87
|
+
@unique_id ||= "accordion-item-#{SecureRandom.hex(4)}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def content_id
|
91
|
+
"#{unique_id}-content"
|
92
|
+
end
|
93
|
+
|
94
|
+
def header_id
|
95
|
+
"#{unique_id}-header"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Attributi per il wrapper dell'item
|
99
|
+
def item_attributes
|
100
|
+
base_classes = [
|
101
|
+
'bui-accordion-item',
|
102
|
+
ITEM_THEME[theme],
|
103
|
+
classes
|
104
|
+
].compact.join(' ')
|
105
|
+
|
106
|
+
{
|
107
|
+
class: base_classes,
|
108
|
+
'data-bui-accordion-target': 'item'
|
109
|
+
}.merge(options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Attributi per il button header
|
113
|
+
def header_attributes
|
114
|
+
base_classes = [
|
115
|
+
'bui-accordion-header',
|
116
|
+
'w-full flex items-center justify-between text-left font-medium transition-colors duration-200',
|
117
|
+
ITEM_SIZE_HEADER[size],
|
118
|
+
ITEM_HEADER_THEME[theme],
|
119
|
+
disabled? ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
|
120
|
+
].compact.join(' ')
|
121
|
+
|
122
|
+
{
|
123
|
+
class: base_classes,
|
124
|
+
type: 'button',
|
125
|
+
id: header_id,
|
126
|
+
'aria-expanded': expanded,
|
127
|
+
'aria-controls': content_id,
|
128
|
+
'data-action': 'click->bui-accordion#toggle',
|
129
|
+
'data-bui-accordion-target': 'trigger',
|
130
|
+
disabled: disabled
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Attributi per il contenuto
|
135
|
+
def content_attributes
|
136
|
+
base_classes = [
|
137
|
+
'bui-accordion-content',
|
138
|
+
'transition-all duration-300 ease-in-out overflow-hidden',
|
139
|
+
ITEM_SIZE_CONTENT[size],
|
140
|
+
expanded? ? 'block' : 'hidden'
|
141
|
+
].compact.join(' ')
|
142
|
+
|
143
|
+
{
|
144
|
+
class: base_classes,
|
145
|
+
id: content_id,
|
146
|
+
'aria-labelledby': header_id,
|
147
|
+
'data-bui-accordion-target': 'content'
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
# Attributi per l'icona
|
152
|
+
def icon_attributes
|
153
|
+
base_classes = [
|
154
|
+
'bui-accordion-icon',
|
155
|
+
'transition-transform duration-200',
|
156
|
+
ITEM_SIZE_ICON[size],
|
157
|
+
expanded? ? 'rotate-180' : ''
|
158
|
+
].compact.join(' ')
|
159
|
+
|
160
|
+
{
|
161
|
+
class: base_classes,
|
162
|
+
'data-bui-accordion-target': 'icon'
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def disabled?
|
167
|
+
disabled
|
168
|
+
end
|
169
|
+
|
170
|
+
def expanded?
|
171
|
+
expanded
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -2,15 +2,15 @@
|
|
2
2
|
<% if link? %>
|
3
3
|
<%= link_to @href, **link_attributes do %>
|
4
4
|
<% if @icon && @icon_position == :left %>
|
5
|
-
<span class="flex-shrink-0 mr-2"><%= render_icon(@icon) %></span>
|
5
|
+
<span class="flex-shrink-0<%= @text.present? ? ' mr-2' : '' %>"><%= render_icon(@icon) %></span>
|
6
6
|
<% end %>
|
7
7
|
|
8
|
-
<% if @
|
9
|
-
<span class="flex-grow"><%= @
|
8
|
+
<% if @text %>
|
9
|
+
<span class="flex-grow"><%= @text %></span>
|
10
10
|
<% end %>
|
11
11
|
|
12
12
|
<% if @icon && @icon_position == :right %>
|
13
|
-
<span class="flex-shrink-0 ml-2"><%= render_icon(@icon) %></span>
|
13
|
+
<span class="flex-shrink-0<%= @text.present? ? ' ml-2' : '' %>"><%= render_icon(@icon) %></span>
|
14
14
|
<% end %>
|
15
15
|
|
16
16
|
<%= content %>
|
@@ -18,15 +18,15 @@
|
|
18
18
|
<% else %>
|
19
19
|
<%= tag.button(**button_attributes) do %>
|
20
20
|
<% if @icon && @icon_position == :left %>
|
21
|
-
<span class="flex-shrink-0 mr-2"><%= render_icon(@icon) %></span>
|
21
|
+
<span class="flex-shrink-0<%= @text.present? ? ' mr-2' : '' %>"><%= render_icon(@icon) %></span>
|
22
22
|
<% end %>
|
23
23
|
|
24
|
-
<% if @
|
25
|
-
<span class="flex-grow"><%= @
|
24
|
+
<% if @text %>
|
25
|
+
<span class="flex-grow"><%= @text %></span>
|
26
26
|
<% end %>
|
27
27
|
|
28
28
|
<% if @icon && @icon_position == :right %>
|
29
|
-
<span class="flex-shrink-0 ml-2"><%= render_icon(@icon) %></span>
|
29
|
+
<span class="flex-shrink-0<%= @text.present? ? ' ml-2' : '' %>"><%= render_icon(@icon) %></span>
|
30
30
|
<% end %>
|
31
31
|
|
32
32
|
<%= content %>
|
@@ -2,7 +2,7 @@ module BetterUi
|
|
2
2
|
module General
|
3
3
|
module Button
|
4
4
|
class Component < ViewComponent::Base
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :text, :theme, :size, :full_width, :disabled,
|
6
6
|
:icon, :icon_position, :href, :method, :data, :classes, :id, :rounded, :button_type, :html_options
|
7
7
|
|
8
8
|
# Classi base sempre presenti
|
@@ -40,8 +40,8 @@ module BetterUi
|
|
40
40
|
|
41
41
|
# Inizializzazione del componente
|
42
42
|
def initialize(
|
43
|
-
|
44
|
-
|
43
|
+
text: nil,
|
44
|
+
theme: :white,
|
45
45
|
size: :medium,
|
46
46
|
full_width: false,
|
47
47
|
disabled: false,
|
@@ -56,8 +56,8 @@ module BetterUi
|
|
56
56
|
button_type: :button,
|
57
57
|
**html_options
|
58
58
|
)
|
59
|
-
@
|
60
|
-
@
|
59
|
+
@text = text
|
60
|
+
@theme = theme.to_sym
|
61
61
|
@size = size.to_sym
|
62
62
|
@full_width = full_width
|
63
63
|
@disabled = disabled
|
@@ -95,7 +95,7 @@ module BetterUi
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def get_button_type_classes
|
98
|
-
BUTTON_THEME[@
|
98
|
+
BUTTON_THEME[@theme] || BUTTON_THEME[:white]
|
99
99
|
end
|
100
100
|
|
101
101
|
def get_border_radius_class
|
@@ -173,21 +173,21 @@ module BetterUi
|
|
173
173
|
|
174
174
|
# Verifica se rendere il componente
|
175
175
|
def render?
|
176
|
-
@
|
176
|
+
@text.present? || @icon.present? || content.present?
|
177
177
|
end
|
178
178
|
|
179
179
|
private
|
180
180
|
|
181
181
|
def validate_params
|
182
|
-
|
182
|
+
validate_theme
|
183
183
|
validate_size
|
184
184
|
validate_icon_position
|
185
185
|
validate_rounded
|
186
186
|
end
|
187
187
|
|
188
|
-
def
|
189
|
-
unless BUTTON_THEME.keys.include?(@
|
190
|
-
raise ArgumentError, "Il
|
188
|
+
def validate_theme
|
189
|
+
unless BUTTON_THEME.keys.include?(@theme)
|
190
|
+
raise ArgumentError, "Il tema deve essere uno tra: #{BUTTON_THEME.keys.join(', ')}"
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|