better_ui 0.2.0 → 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.
- checksums.yaml +4 -4
- data/app/components/better_ui/application/sidebar/component.html.erb +53 -16
- data/app/components/better_ui/application/sidebar/component.rb +3 -2
- 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 +4 -4
- data/app/components/better_ui/general/dropdown/component.html.erb +18 -7
- data/app/components/better_ui/general/dropdown/component.rb +8 -57
- data/app/components/better_ui/general/dropdown/item_component.rb +2 -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/helpers/better_ui/application_helper.rb +4 -4
- data/app/helpers/better_ui/general/components/accordion/accordion_helper.rb +73 -0
- data/app/helpers/better_ui/general/components/accordion.rb +11 -0
- data/app/helpers/better_ui/general/components/modal/modal_helper.rb +34 -44
- data/app/helpers/better_ui/general/components/modal.rb +11 -0
- data/app/helpers/better_ui/general/components/tabs/tabs_helper.rb +59 -26
- data/lib/better_ui/version.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f8b92fdba76f5e84f09fb6a3bf9879b209e0e7dfd8f864186784db24b808c59
|
4
|
+
data.tar.gz: 4d21e971baeecb10c7a3c5952041ac7e6911342e0efe4d0015efb92f9f214652
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 778ee8d067130b37947a227ccf1322e8cf5b82d19c9b3f7a0371cc16b62071daaeb5eb6a191898ef2b06419486fe656bc22bea4314fbf98884fdf16259ac8409
|
7
|
+
data.tar.gz: 1df545b428bdb281fb6f78b7be2e58e455bd6bd67f0202684f51b9e756c9ccc3e928a283f97d98a924185e1bb859b63f8cf2a91857ef49478cf6bfc3f254629d
|
@@ -1,10 +1,21 @@
|
|
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
|
+
|
7
|
+
<!-- Mobile Overlay -->
|
8
|
+
<div data-bui-sidebar-target="overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden md:hidden"></div>
|
9
|
+
|
10
|
+
<!-- Sidebar Container -->
|
11
|
+
<aside data-bui-sidebar-target="container" class="<%= container_classes %>">
|
2
12
|
<!-- Header Section -->
|
3
13
|
<% if has_header? %>
|
4
14
|
<div class="px-6 py-4 border-b border-gray-200">
|
5
|
-
|
6
|
-
|
7
|
-
<div class="flex-
|
15
|
+
<div class="flex items-center justify-between">
|
16
|
+
<% if header[:logo].present? %>
|
17
|
+
<div class="flex items-center">
|
18
|
+
<div class="flex-shrink-0">
|
8
19
|
<% if header[:logo].is_a?(Hash) %>
|
9
20
|
<%= bui_avatar(**header[:logo]) %>
|
10
21
|
<% else %>
|
@@ -20,19 +31,30 @@
|
|
20
31
|
</div>
|
21
32
|
<% end %>
|
22
33
|
</div>
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
<% elsif header[:title].present? %>
|
35
|
+
<div>
|
36
|
+
<h2 class="text-lg font-semibold text-gray-900"><%= header[:title] %></h2>
|
37
|
+
<% if header[:subtitle].present? %>
|
38
|
+
<p class="text-sm text-gray-500"><%= header[:subtitle] %></p>
|
39
|
+
<% end %>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
42
|
+
|
43
|
+
<!-- Collapse Button (solo se collapsible) -->
|
44
|
+
<% if collapsible %>
|
45
|
+
<%= bui_button(
|
46
|
+
icon: "arrow-left",
|
47
|
+
type: :white,
|
48
|
+
size: :small,
|
49
|
+
title: "Comprimi sidebar"
|
50
|
+
) %>
|
51
|
+
<% end %>
|
52
|
+
</div>
|
31
53
|
</div>
|
32
54
|
<% end %>
|
33
55
|
|
34
56
|
<!-- Navigation Section -->
|
35
|
-
<nav class="flex-1 px-4 py-6 space-y-6">
|
57
|
+
<nav class="flex-1 px-4 py-6 space-y-6 overflow-y-auto">
|
36
58
|
<% navigation_sections.each do |section| %>
|
37
59
|
<div class="space-y-2">
|
38
60
|
<!-- Section Title -->
|
@@ -57,7 +79,9 @@
|
|
57
79
|
<button
|
58
80
|
type="button"
|
59
81
|
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-
|
82
|
+
data-bui-sidebar-target="sectionTrigger"
|
83
|
+
data-bui-sidebar-section-id="<%= item[:id] %>"
|
84
|
+
data-action="click->bui-sidebar#toggleSection"
|
61
85
|
aria-expanded="<%= item[:expanded] || false %>"
|
62
86
|
>
|
63
87
|
<div class="flex items-center">
|
@@ -68,13 +92,14 @@
|
|
68
92
|
<% end %>
|
69
93
|
<span><%= item[:label] %></span>
|
70
94
|
</div>
|
71
|
-
<span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>">
|
95
|
+
<span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>" data-bui-sidebar-chevron>
|
72
96
|
<%= bui_icon("chevron-right", size: :small) %>
|
73
97
|
</span>
|
74
98
|
</button>
|
75
99
|
|
76
100
|
<div
|
77
|
-
|
101
|
+
data-bui-sidebar-target="sectionContent"
|
102
|
+
data-bui-sidebar-section-id="<%= item[:id] %>"
|
78
103
|
class="<%= item[:expanded] ? 'block' : 'hidden' %> mt-1 space-y-1"
|
79
104
|
>
|
80
105
|
<% (item[:children] || []).each do |child| %>
|
@@ -187,4 +212,16 @@
|
|
187
212
|
<% end %>
|
188
213
|
</div>
|
189
214
|
<% end %>
|
215
|
+
|
216
|
+
<!-- Resize Handle (solo se NON collapsible) -->
|
217
|
+
<% unless collapsible %>
|
218
|
+
<div data-bui-sidebar-target="resizeHandle"
|
219
|
+
class="absolute top-0 right-0 w-1 h-full bg-transparent hover:bg-blue-500 cursor-col-resize transition-colors duration-150 group">
|
220
|
+
<div class="absolute inset-y-0 -right-1 w-3 flex items-center justify-center">
|
221
|
+
<div class="w-0.5 h-8 bg-gray-300 group-hover:bg-blue-500 transition-colors duration-150"></div>
|
222
|
+
</div>
|
223
|
+
</div>
|
224
|
+
<% end %>
|
190
225
|
</aside>
|
226
|
+
|
227
|
+
</div>
|
@@ -4,9 +4,10 @@ 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 e bui_button
|
8
8
|
include BetterUi::General::Components::Icon::IconHelper
|
9
9
|
include BetterUi::General::Components::Avatar::AvatarHelper
|
10
|
+
include BetterUi::General::Components::Button::ButtonHelper
|
10
11
|
attr_reader :width, :position, :theme, :shadow, :border, :header, :footer, :navigation_sections, :collapsible, :classes
|
11
12
|
|
12
13
|
# Larghezze sidebar con classi Tailwind dirette
|
@@ -74,7 +75,7 @@ module BetterUi
|
|
74
75
|
end
|
75
76
|
|
76
77
|
def container_classes
|
77
|
-
base_classes = %w[fixed inset-y-0 z-50 flex flex-col
|
78
|
+
base_classes = %w[fixed inset-y-0 h-screen z-50 flex flex-col]
|
78
79
|
|
79
80
|
# Posizione
|
80
81
|
base_classes << (position == :right ? "right-0" : "left-0")
|
@@ -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,7 +2,7 @@
|
|
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<%= @label.present? ? ' mr-2' : '' %>"><%= render_icon(@icon) %></span>
|
6
6
|
<% end %>
|
7
7
|
|
8
8
|
<% if @label %>
|
@@ -10,7 +10,7 @@
|
|
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<%= @label.present? ? ' ml-2' : '' %>"><%= render_icon(@icon) %></span>
|
14
14
|
<% end %>
|
15
15
|
|
16
16
|
<%= content %>
|
@@ -18,7 +18,7 @@
|
|
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<%= @label.present? ? ' mr-2' : '' %>"><%= render_icon(@icon) %></span>
|
22
22
|
<% end %>
|
23
23
|
|
24
24
|
<% if @label %>
|
@@ -26,7 +26,7 @@
|
|
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<%= @label.present? ? ' ml-2' : '' %>"><%= render_icon(@icon) %></span>
|
30
30
|
<% end %>
|
31
31
|
|
32
32
|
<%= content %>
|
@@ -1,13 +1,24 @@
|
|
1
|
-
<div <%=
|
2
|
-
|
1
|
+
<div class="relative inline-block <%= @classes %>"
|
2
|
+
data-controller="bui-dropdown"
|
3
|
+
data-bui-dropdown-open-value="false"
|
4
|
+
<%= tag.attributes(@html_options.except(:class)) %>>
|
5
|
+
|
6
|
+
<button type="button"
|
7
|
+
class="inline-flex items-center justify-center border font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors <%= dynamic_trigger_classes %>"
|
8
|
+
data-bui-dropdown-target="trigger"
|
9
|
+
data-action="click->bui-dropdown#toggle keydown->bui-dropdown#keydown"
|
10
|
+
aria-expanded="false"
|
11
|
+
aria-haspopup="true">
|
3
12
|
<%= @trigger %>
|
4
|
-
|
5
|
-
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
6
|
-
</svg>
|
13
|
+
<%= bui_icon("chevron-down", size: :small, classes: "ml-2 -mr-1") %>
|
7
14
|
</button>
|
8
15
|
|
9
|
-
<div <%=
|
10
|
-
|
16
|
+
<div class="absolute z-50 mt-2 origin-top-right bg-white border border-gray-200 shadow-lg focus:outline-none <%= dynamic_menu_classes %>"
|
17
|
+
data-bui-dropdown-target="menu"
|
18
|
+
role="menu"
|
19
|
+
aria-orientation="vertical"
|
20
|
+
style="display: none;">
|
21
|
+
<div class="py-1" role="none" data-action="click->bui-dropdown#itemClick">
|
11
22
|
<%= content %>
|
12
23
|
</div>
|
13
24
|
</div>
|
@@ -4,16 +4,11 @@ module BetterUi
|
|
4
4
|
module General
|
5
5
|
module Dropdown
|
6
6
|
class Component < ViewComponent::Base
|
7
|
+
include BetterUi::General::Components::Icon::IconHelper
|
8
|
+
|
7
9
|
attr_reader :trigger, :position, :theme, :size, :rounded, :animation, :classes, :html_options
|
8
10
|
|
9
|
-
# Classi base per
|
10
|
-
DROPDOWN_CONTAINER_CLASSES = "relative inline-block"
|
11
|
-
|
12
|
-
# Classi base per il pulsante trigger
|
13
|
-
DROPDOWN_TRIGGER_BASE_CLASSES = "inline-flex items-center justify-center border font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 transition-colors"
|
14
|
-
|
15
|
-
# Classi base per il menu dropdown
|
16
|
-
DROPDOWN_MENU_BASE_CLASSES = "absolute z-50 mt-2 origin-top-right bg-white border border-gray-200 shadow-lg focus:outline-none"
|
11
|
+
# Classi base spostate nel template HTML per migliore leggibilità
|
17
12
|
|
18
13
|
# Temi per il trigger del dropdown con classi Tailwind dirette
|
19
14
|
DROPDOWN_TRIGGER_THEME = {
|
@@ -81,69 +76,25 @@ module BetterUi
|
|
81
76
|
validate_params
|
82
77
|
end
|
83
78
|
|
84
|
-
#
|
85
|
-
def
|
79
|
+
# Restituisce solo le classi dinamiche per il trigger
|
80
|
+
def dynamic_trigger_classes
|
86
81
|
[
|
87
|
-
DROPDOWN_CONTAINER_CLASSES,
|
88
|
-
@classes
|
89
|
-
].compact.join(" ")
|
90
|
-
end
|
91
|
-
|
92
|
-
# Combina tutte le classi per il trigger
|
93
|
-
def trigger_classes
|
94
|
-
[
|
95
|
-
DROPDOWN_TRIGGER_BASE_CLASSES,
|
96
82
|
get_trigger_theme_classes,
|
97
83
|
get_trigger_size_classes,
|
98
84
|
get_trigger_rounded_classes
|
99
85
|
].compact.join(" ")
|
100
86
|
end
|
101
87
|
|
102
|
-
#
|
103
|
-
def
|
88
|
+
# Restituisce solo le classi dinamiche per il menu
|
89
|
+
def dynamic_menu_classes
|
104
90
|
[
|
105
|
-
DROPDOWN_MENU_BASE_CLASSES,
|
106
91
|
get_position_classes,
|
107
92
|
get_animation_classes,
|
108
93
|
get_menu_rounded_classes
|
109
94
|
].compact.join(" ")
|
110
95
|
end
|
111
96
|
|
112
|
-
#
|
113
|
-
def container_attributes
|
114
|
-
attrs = {
|
115
|
-
class: container_classes,
|
116
|
-
"data-dropdown": true
|
117
|
-
}
|
118
|
-
|
119
|
-
@html_options.except(:class).each do |key, value|
|
120
|
-
attrs[key] = value
|
121
|
-
end
|
122
|
-
|
123
|
-
attrs
|
124
|
-
end
|
125
|
-
|
126
|
-
# Restituisce gli attributi per il trigger
|
127
|
-
def trigger_attributes
|
128
|
-
{
|
129
|
-
type: "button",
|
130
|
-
class: trigger_classes,
|
131
|
-
"data-dropdown-trigger": true,
|
132
|
-
"aria-expanded": "false",
|
133
|
-
"aria-haspopup": "true"
|
134
|
-
}
|
135
|
-
end
|
136
|
-
|
137
|
-
# Restituisce gli attributi per il menu
|
138
|
-
def menu_attributes
|
139
|
-
{
|
140
|
-
class: menu_classes,
|
141
|
-
"data-dropdown-menu": true,
|
142
|
-
role: "menu",
|
143
|
-
"aria-orientation": "vertical",
|
144
|
-
style: "display: none;"
|
145
|
-
}
|
146
|
-
end
|
97
|
+
# Metodi per attributi rimossi - ora gestiti direttamente nel template HTML
|
147
98
|
|
148
99
|
# Verifica se rendere il componente
|
149
100
|
def render?
|
@@ -1,42 +1,5 @@
|
|
1
|
-
<%#
|
2
|
-
|
3
|
-
<%=
|
4
|
-
|
5
|
-
|
6
|
-
<%= tag.div **header_attributes do %>
|
7
|
-
<h3 class="text-lg font-semibold" id="modal-title"><%= @title %></h3>
|
8
|
-
<% if @closable %>
|
9
|
-
<button type="button" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 transition-colors duration-200" aria-label="Chiudi modal">
|
10
|
-
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
11
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
12
|
-
</svg>
|
13
|
-
</button>
|
14
|
-
<% end %>
|
15
|
-
<% end %>
|
16
|
-
|
17
|
-
<%# Body del modal %>
|
18
|
-
<div class="p-6">
|
19
|
-
<%= content %>
|
20
|
-
</div>
|
21
|
-
<% end %>
|
22
|
-
<% end %>
|
23
|
-
<% else %>
|
24
|
-
<%= tag.div **container_attributes do %>
|
25
|
-
<%# Header del modal %>
|
26
|
-
<%= tag.div **header_attributes do %>
|
27
|
-
<h3 class="text-lg font-semibold" id="modal-title"><%= @title %></h3>
|
28
|
-
<% if @closable %>
|
29
|
-
<button type="button" class="text-gray-400 hover:text-gray-600 focus:outline-none focus:text-gray-600 transition-colors duration-200" aria-label="Chiudi modal">
|
30
|
-
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
31
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
32
|
-
</svg>
|
33
|
-
</button>
|
34
|
-
<% end %>
|
35
|
-
<% end %>
|
36
|
-
|
37
|
-
<%# Body del modal %>
|
38
|
-
<div class="p-6">
|
39
|
-
<%= content %>
|
40
|
-
</div>
|
41
|
-
<% end %>
|
42
|
-
<% end %>
|
1
|
+
<%# Wrapper component con controller Stimulus e slots %>
|
2
|
+
<div <%= tag.attributes(wrapper_attributes) %>>
|
3
|
+
<%= trigger %>
|
4
|
+
<%= modal %>
|
5
|
+
</div>
|