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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +225 -119
- data/app/assets/stylesheets/better_ui/application.css +0 -356
- data/app/components/better_ui/application/card/component.html.erb +20 -0
- data/app/components/better_ui/application/card/component.rb +214 -0
- data/app/components/better_ui/application/main/component.html.erb +9 -0
- data/app/components/better_ui/application/main/component.rb +123 -0
- data/app/components/better_ui/application/navbar/component.html.erb +92 -0
- data/app/components/better_ui/application/navbar/component.rb +136 -0
- data/app/components/better_ui/application/sidebar/component.html.erb +190 -0
- data/app/components/better_ui/application/sidebar/component.rb +129 -0
- data/app/components/better_ui/general/alert/component.html.erb +32 -0
- data/app/components/better_ui/general/alert/component.rb +242 -0
- data/app/components/better_ui/general/avatar/component.html.erb +20 -0
- data/app/components/better_ui/general/avatar/component.rb +301 -0
- data/app/components/better_ui/general/badge/component.html.erb +23 -0
- data/app/components/better_ui/general/badge/component.rb +248 -0
- data/app/components/better_ui/general/breadcrumb/component.html.erb +15 -0
- data/app/components/better_ui/general/breadcrumb/component.rb +187 -0
- data/app/components/better_ui/general/button/component.html.erb +34 -0
- data/app/components/better_ui/general/button/component.rb +214 -0
- data/app/components/better_ui/general/divider/component.html.erb +10 -0
- data/app/components/better_ui/general/divider/component.rb +226 -0
- data/app/components/better_ui/general/field/component.html.erb +27 -0
- data/app/components/better_ui/general/field/component.rb +37 -0
- data/app/components/better_ui/general/heading/component.html.erb +22 -0
- data/app/components/better_ui/general/heading/component.rb +257 -0
- data/app/components/better_ui/general/icon/component.html.erb +7 -0
- data/app/components/better_ui/general/icon/component.rb +239 -0
- data/app/components/better_ui/general/input/checkbox/component.html.erb +5 -0
- data/app/components/better_ui/general/input/checkbox/component.rb +238 -0
- data/app/components/better_ui/general/input/datetime/component.html.erb +5 -0
- data/app/components/better_ui/general/input/datetime/component.rb +223 -0
- data/app/components/better_ui/general/input/radio/component.html.erb +5 -0
- data/app/components/better_ui/general/input/radio/component.rb +230 -0
- data/app/components/better_ui/general/input/select/component.html.erb +16 -0
- data/app/components/better_ui/general/input/select/component.rb +184 -0
- data/app/components/better_ui/general/input/select/select_component.html.erb +5 -0
- data/app/components/better_ui/general/input/select/select_component.rb +37 -0
- data/app/components/better_ui/general/input/text/component.html.erb +5 -0
- data/app/components/better_ui/general/input/text/component.rb +171 -0
- data/app/components/better_ui/general/input/textarea/component.html.erb +5 -0
- data/app/components/better_ui/general/input/textarea/component.rb +166 -0
- data/app/components/better_ui/general/link/component.html.erb +18 -0
- data/app/components/better_ui/general/link/component.rb +258 -0
- data/app/components/better_ui/general/panel/component.html.erb +28 -0
- data/app/components/better_ui/general/panel/component.rb +249 -0
- data/app/components/better_ui/general/progress/component.html.erb +11 -0
- data/app/components/better_ui/general/progress/component.rb +160 -0
- data/app/components/better_ui/general/spinner/component.html.erb +35 -0
- data/app/components/better_ui/general/spinner/component.rb +93 -0
- data/app/components/better_ui/general/table/component.html.erb +5 -0
- data/app/components/better_ui/general/table/component.rb +217 -0
- data/app/components/better_ui/general/table/tbody_component.html.erb +3 -0
- data/app/components/better_ui/general/table/tbody_component.rb +30 -0
- data/app/components/better_ui/general/table/td_component.html.erb +3 -0
- data/app/components/better_ui/general/table/td_component.rb +44 -0
- data/app/components/better_ui/general/table/tfoot_component.html.erb +3 -0
- data/app/components/better_ui/general/table/tfoot_component.rb +28 -0
- data/app/components/better_ui/general/table/th_component.html.erb +6 -0
- data/app/components/better_ui/general/table/th_component.rb +51 -0
- data/app/components/better_ui/general/table/thead_component.html.erb +3 -0
- data/app/components/better_ui/general/table/thead_component.rb +28 -0
- data/app/components/better_ui/general/table/tr_component.html.erb +3 -0
- data/app/components/better_ui/general/table/tr_component.rb +30 -0
- data/app/components/better_ui/general/tag/component.html.erb +3 -0
- data/app/components/better_ui/general/tag/component.rb +104 -0
- data/app/components/better_ui/general/tooltip/component.html.erb +7 -0
- data/app/components/better_ui/general/tooltip/component.rb +239 -0
- data/app/helpers/better_ui/application/components/card/card_helper.rb +96 -0
- data/app/helpers/better_ui/application/components/card.rb +11 -0
- data/app/helpers/better_ui/application/components/main/main_helper.rb +64 -0
- data/app/helpers/better_ui/application/components/navbar/navbar_helper.rb +77 -0
- data/app/helpers/better_ui/application/components/sidebar/sidebar_helper.rb +51 -0
- data/app/helpers/better_ui/application_helper.rb +42 -179
- data/app/helpers/better_ui/general/components/alert/alert_helper.rb +57 -0
- data/app/helpers/better_ui/general/components/avatar/avatar_helper.rb +29 -0
- data/app/helpers/better_ui/general/components/badge/badge_helper.rb +53 -0
- data/app/helpers/better_ui/general/components/breadcrumb/breadcrumb_helper.rb +37 -0
- data/app/helpers/better_ui/general/components/button/button_helper.rb +65 -0
- data/app/helpers/better_ui/general/components/container/container_helper.rb +60 -0
- data/app/helpers/better_ui/general/components/divider/divider_helper.rb +63 -0
- data/app/helpers/better_ui/general/components/field/field_helper.rb +26 -0
- data/app/helpers/better_ui/general/components/heading/heading_helper.rb +72 -0
- data/app/helpers/better_ui/general/components/icon/icon_helper.rb +16 -0
- data/app/helpers/better_ui/general/components/input/checkbox/checkbox_helper.rb +81 -0
- data/app/helpers/better_ui/general/components/input/datetime/datetime_helper.rb +91 -0
- data/app/helpers/better_ui/general/components/input/radio/radio_helper.rb +79 -0
- data/app/helpers/better_ui/general/components/input/radio_group/radio_group_helper.rb +124 -0
- data/app/helpers/better_ui/general/components/input/select/select_helper.rb +70 -0
- data/app/helpers/better_ui/general/components/input/text/text_helper.rb +138 -0
- data/app/helpers/better_ui/general/components/input/textarea/textarea_helper.rb +73 -0
- data/app/helpers/better_ui/general/components/link/link_helper.rb +89 -0
- data/app/helpers/better_ui/general/components/panel/panel_helper.rb +83 -0
- data/app/helpers/better_ui/general/components/progress/progress_helper.rb +53 -0
- data/app/helpers/better_ui/general/components/spinner/spinner_helper.rb +19 -0
- data/app/helpers/better_ui/general/components/table/table_helper.rb +53 -0
- data/app/helpers/better_ui/general/components/table/tbody_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/table/td_helper.rb +19 -0
- data/app/helpers/better_ui/general/components/table/tfoot_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/table/th_helper.rb +19 -0
- data/app/helpers/better_ui/general/components/table/thead_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/table/tr_helper.rb +13 -0
- data/app/helpers/better_ui/general/components/tag/tag_helper.rb +26 -0
- data/app/helpers/better_ui/general/components/tooltip/tooltip_helper.rb +60 -0
- data/app/views/layouts/better_ui/application.html.erb +6 -124
- data/config/initializers/lookbook.rb +23 -0
- data/config/routes.rb +0 -8
- data/lib/better_ui/engine.rb +5 -19
- data/lib/better_ui/railtie.rb +20 -0
- data/lib/better_ui/version.rb +1 -1
- data/lib/better_ui.rb +4 -20
- metadata +131 -28
- data/app/controllers/better_ui/docs_controller.rb +0 -41
- data/app/views/better_ui/docs/component.html.erb +0 -365
- data/app/views/better_ui/docs/index.html.erb +0 -100
- data/app/views/better_ui/docs/show.html.erb +0 -60
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module Application
|
5
|
+
module Main
|
6
|
+
class Component < ViewComponent::Base
|
7
|
+
attr_reader :padding, :inner_padding, :rounded, :shadow, :with_sidebar, :sidebar_width, :classes
|
8
|
+
|
9
|
+
# Arrotondamento bordi con classi Tailwind dirette
|
10
|
+
MAIN_ROUNDED = {
|
11
|
+
none: "",
|
12
|
+
small: "rounded-lg",
|
13
|
+
medium: "rounded-xl",
|
14
|
+
large: "rounded-2xl",
|
15
|
+
full: "rounded-full"
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
# Ombre con classi Tailwind dirette
|
19
|
+
MAIN_SHADOW = {
|
20
|
+
none: "",
|
21
|
+
small: "shadow-sm",
|
22
|
+
medium: "shadow-md",
|
23
|
+
large: "shadow-lg"
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
# Padding con classi Tailwind dirette
|
27
|
+
MAIN_PADDING = {
|
28
|
+
true: "p-6",
|
29
|
+
false: "p-0"
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# Classi layout per sidebar
|
33
|
+
MAIN_LAYOUT = {
|
34
|
+
sidebar: { sm: "pl-48", md: "pl-64", lg: "pl-72", xl: "pl-80" }, # Con Sidebar
|
35
|
+
none: "" # Senza sidebar
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
# Larghezza sidebar (deve corrispondere a SIDEBAR_WIDTHS del componente Sidebar)
|
39
|
+
MAIN_SIDEBAR_WIDTH = {
|
40
|
+
sm: 48,
|
41
|
+
md: 64,
|
42
|
+
lg: 72,
|
43
|
+
xl: 80
|
44
|
+
}.freeze
|
45
|
+
|
46
|
+
# @param padding [Boolean] Se applicare il padding al contenitore principale
|
47
|
+
# @param inner_padding [Boolean] Se applicare il padding al contenitore interno
|
48
|
+
# @param rounded [Symbol] Tipo di border-radius del contenitore interno (:none, :small, :medium, :large, :full), default :small
|
49
|
+
# @param shadow [Symbol] Tipo di ombra del contenitore interno (:none, :small, :medium, :large), default :medium
|
50
|
+
# @param with_sidebar [Boolean] Se lasciare lo spazio per la sidebar
|
51
|
+
# @param sidebar_width [Symbol] Larghezza della sidebar (:sm, :md, :lg, :xl), default :md
|
52
|
+
# @param classes [String] Classi CSS aggiuntive per il contenitore principale
|
53
|
+
def initialize(
|
54
|
+
padding: true,
|
55
|
+
inner_padding: true,
|
56
|
+
rounded: :small,
|
57
|
+
shadow: :medium,
|
58
|
+
with_sidebar: true,
|
59
|
+
sidebar_width: :md,
|
60
|
+
classes: nil
|
61
|
+
)
|
62
|
+
@padding = padding
|
63
|
+
@inner_padding = inner_padding
|
64
|
+
@rounded = rounded.to_sym
|
65
|
+
@shadow = shadow.to_sym
|
66
|
+
@with_sidebar = with_sidebar
|
67
|
+
@sidebar_width = sidebar_width.to_sym
|
68
|
+
@classes = classes
|
69
|
+
|
70
|
+
validate_params
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def get_layout_class
|
78
|
+
if with_sidebar
|
79
|
+
MAIN_LAYOUT[:sidebar][@sidebar_width] || MAIN_LAYOUT[:sidebar][:md]
|
80
|
+
else
|
81
|
+
MAIN_LAYOUT[:none]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_padding_class(enabled)
|
86
|
+
MAIN_PADDING[enabled] || MAIN_PADDING[false]
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_rounded_class
|
90
|
+
MAIN_ROUNDED[@rounded] || MAIN_ROUNDED[:small]
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_shadow_class
|
94
|
+
MAIN_SHADOW[@shadow] || MAIN_SHADOW[:medium]
|
95
|
+
end
|
96
|
+
|
97
|
+
def validate_params
|
98
|
+
validate_rounded
|
99
|
+
validate_shadow
|
100
|
+
validate_sidebar_width if with_sidebar
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_rounded
|
104
|
+
unless MAIN_ROUNDED.keys.include?(@rounded)
|
105
|
+
raise ArgumentError, "L'arrotondamento deve essere uno tra: #{MAIN_ROUNDED.keys.join(', ')}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_shadow
|
110
|
+
unless MAIN_SHADOW.keys.include?(@shadow)
|
111
|
+
raise ArgumentError, "L'ombra deve essere una tra: #{MAIN_SHADOW.keys.join(', ')}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate_sidebar_width
|
116
|
+
unless MAIN_SIDEBAR_WIDTH.keys.include?(@sidebar_width)
|
117
|
+
raise ArgumentError, "La larghezza della sidebar deve essere una tra: #{MAIN_SIDEBAR_WIDTH.keys.join(', ')}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
<header class="<%= container_classes %>">
|
2
|
+
<div class="flex items-center justify-between">
|
3
|
+
<!-- Left side - custom content-->
|
4
|
+
<div class="flex items-center">
|
5
|
+
<%= content %>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<!-- User actions -->
|
9
|
+
<div class="flex items-center space-x-4">
|
10
|
+
<% if has_actions? %>
|
11
|
+
<% actions.each do |action| %>
|
12
|
+
<div class="flex-shrink-0">
|
13
|
+
<% if action[:type] == :button %>
|
14
|
+
<% if action[:href].present? %>
|
15
|
+
<a
|
16
|
+
href="<%= action[:href] %>"
|
17
|
+
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 <%= action[:theme] == :primary ? 'bg-gray-900 text-white hover:bg-gray-700' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' %>"
|
18
|
+
>
|
19
|
+
<% if action[:icon].present? %>
|
20
|
+
<%= bui_icon(action[:icon], size: :small) %>
|
21
|
+
<% if action[:label].present? %>
|
22
|
+
<span class="ml-2"><%= action[:label] %></span>
|
23
|
+
<% end %>
|
24
|
+
<% else %>
|
25
|
+
<%= action[:label] %>
|
26
|
+
<% end %>
|
27
|
+
</a>
|
28
|
+
<% else %>
|
29
|
+
<button
|
30
|
+
type="button"
|
31
|
+
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 <%= action[:theme] == :primary ? 'bg-gray-900 text-white hover:bg-gray-700' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' %>"
|
32
|
+
<% if action[:data].present? %>
|
33
|
+
<% action[:data].each do |key, value| %>
|
34
|
+
data-<%= key %>="<%= value %>"
|
35
|
+
<% end %>
|
36
|
+
<% end %>
|
37
|
+
>
|
38
|
+
<% if action[:icon].present? %>
|
39
|
+
<%= bui_icon(action[:icon], size: :small) %>
|
40
|
+
<% if action[:label].present? %>
|
41
|
+
<span class="ml-2"><%= action[:label] %></span>
|
42
|
+
<% end %>
|
43
|
+
<% else %>
|
44
|
+
<%= action[:label] %>
|
45
|
+
<% end %>
|
46
|
+
</button>
|
47
|
+
<% end %>
|
48
|
+
<% elsif action[:type] == :avatar %>
|
49
|
+
<% if action[:href].present? %>
|
50
|
+
<a href="<%= action[:href] %>" class="flex-shrink-0 hover:opacity-75 transition-opacity duration-150">
|
51
|
+
<% end %>
|
52
|
+
|
53
|
+
<% if action[:avatar].is_a?(Hash) %>
|
54
|
+
<%= bui_avatar(**action[:avatar]) %>
|
55
|
+
<% else %>
|
56
|
+
<%= action[:avatar].html_safe %>
|
57
|
+
<% end %>
|
58
|
+
|
59
|
+
<% if action[:href].present? %>
|
60
|
+
</a>
|
61
|
+
<% end %>
|
62
|
+
<% elsif action[:type] == :icon %>
|
63
|
+
<% if action[:href].present? %>
|
64
|
+
<a
|
65
|
+
href="<%= action[:href] %>"
|
66
|
+
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-md transition-colors duration-150"
|
67
|
+
>
|
68
|
+
<%= bui_icon(action[:icon], size: :medium) %>
|
69
|
+
</a>
|
70
|
+
<% else %>
|
71
|
+
<button
|
72
|
+
type="button"
|
73
|
+
class="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-md transition-colors duration-150"
|
74
|
+
<% if action[:data].present? %>
|
75
|
+
<% action[:data].each do |key, value| %>
|
76
|
+
data-<%= key %>="<%= value %>"
|
77
|
+
<% end %>
|
78
|
+
<% end %>
|
79
|
+
>
|
80
|
+
<%= bui_icon(action[:icon], size: :medium) %>
|
81
|
+
</button>
|
82
|
+
<% end %>
|
83
|
+
<% else %>
|
84
|
+
<!-- Custom content -->
|
85
|
+
<%= action[:content].html_safe if action[:content].present? %>
|
86
|
+
<% end %>
|
87
|
+
</div>
|
88
|
+
<% end %>
|
89
|
+
<% end %>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
</header>
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module Application
|
5
|
+
module Navbar
|
6
|
+
class Component < ViewComponent::Base
|
7
|
+
# Include degli helper per utilizzare bui_icon e bui_avatar
|
8
|
+
include BetterUi::General::Components::Icon::IconHelper
|
9
|
+
include BetterUi::General::Components::Avatar::AvatarHelper
|
10
|
+
|
11
|
+
attr_reader :theme, :shadow, :border, :actions, :classes, :with_sidebar, :sidebar_width
|
12
|
+
|
13
|
+
# Temi navbar con classi Tailwind dirette
|
14
|
+
NAVBAR_THEME = {
|
15
|
+
default: "bg-white text-gray-900 border-gray-200",
|
16
|
+
white: "bg-white text-gray-900 border-gray-200",
|
17
|
+
red: "bg-red-50 text-red-900 border-red-200",
|
18
|
+
rose: "bg-rose-50 text-rose-900 border-rose-200",
|
19
|
+
orange: "bg-orange-50 text-orange-900 border-orange-200",
|
20
|
+
green: "bg-green-50 text-green-900 border-green-200",
|
21
|
+
blue: "bg-blue-50 text-blue-900 border-blue-200",
|
22
|
+
yellow: "bg-yellow-50 text-yellow-900 border-yellow-200",
|
23
|
+
violet: "bg-violet-50 text-violet-900 border-violet-200"
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
# Ombre navbar con classi Tailwind dirette
|
27
|
+
NAVBAR_SHADOW = {
|
28
|
+
none: "",
|
29
|
+
small: "shadow-sm",
|
30
|
+
medium: "shadow-md",
|
31
|
+
large: "shadow-lg"
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
# Larghezze sidebar con valori numerici per utilizzo in classi custom
|
35
|
+
NAVBAR_SIDEBAR_WIDTH = {
|
36
|
+
sm: 48,
|
37
|
+
md: 64,
|
38
|
+
lg: 72,
|
39
|
+
xl: 80
|
40
|
+
}.freeze
|
41
|
+
|
42
|
+
# @param theme [Symbol] Tema colori (default, white, red, rose, orange, green, blue, yellow, violet), default :default
|
43
|
+
# @param shadow [Symbol] Tipo di ombra (none, small, medium, large), default :small
|
44
|
+
# @param border [Boolean] Se mostrare il bordo inferiore, default true
|
45
|
+
# @param actions [Array] Array di azioni/pulsanti a destra
|
46
|
+
# @param classes [String] Classi CSS aggiuntive
|
47
|
+
# @param with_sidebar [Boolean] Se la navbar è affiancata a una sidebar, default false
|
48
|
+
# @param sidebar_width [Symbol] Larghezza della sidebar affiancata (:sm, :md, :lg, :xl), default :md
|
49
|
+
def initialize(
|
50
|
+
theme: :default,
|
51
|
+
shadow: :small,
|
52
|
+
border: false,
|
53
|
+
actions: [],
|
54
|
+
classes: nil,
|
55
|
+
with_sidebar: false,
|
56
|
+
sidebar_width: :md
|
57
|
+
)
|
58
|
+
@theme = theme.to_sym
|
59
|
+
@shadow = shadow.to_sym
|
60
|
+
@border = border
|
61
|
+
@actions = actions || []
|
62
|
+
@classes = classes
|
63
|
+
@with_sidebar = with_sidebar
|
64
|
+
@sidebar_width = sidebar_width.to_sym
|
65
|
+
|
66
|
+
validate_params
|
67
|
+
end
|
68
|
+
|
69
|
+
def container_classes
|
70
|
+
base_classes = %w[h-[81px] px-6 py-4]
|
71
|
+
|
72
|
+
# Width
|
73
|
+
if with_sidebar
|
74
|
+
sidebar_width_value = NAVBAR_SIDEBAR_WIDTH[@sidebar_width] || NAVBAR_SIDEBAR_WIDTH[:md]
|
75
|
+
base_classes << "pl-#{sidebar_width_value}"
|
76
|
+
base_classes << "ml-auto"
|
77
|
+
else
|
78
|
+
base_classes << "w-full"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Tema
|
82
|
+
base_classes.concat(theme_classes.split)
|
83
|
+
|
84
|
+
# Shadow
|
85
|
+
base_classes << shadow_class if shadow != :none
|
86
|
+
|
87
|
+
# Border
|
88
|
+
base_classes << "border-b" if border
|
89
|
+
|
90
|
+
# Classi aggiuntive
|
91
|
+
base_classes << classes if classes.present?
|
92
|
+
|
93
|
+
base_classes.compact.join(" ")
|
94
|
+
end
|
95
|
+
|
96
|
+
def has_actions?
|
97
|
+
actions.present? && actions.any?
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def theme_classes
|
103
|
+
NAVBAR_THEME[@theme] || NAVBAR_THEME[:default]
|
104
|
+
end
|
105
|
+
|
106
|
+
def shadow_class
|
107
|
+
NAVBAR_SHADOW[@shadow] || NAVBAR_SHADOW[:small]
|
108
|
+
end
|
109
|
+
|
110
|
+
def validate_params
|
111
|
+
validate_theme
|
112
|
+
validate_shadow
|
113
|
+
validate_sidebar_width if with_sidebar
|
114
|
+
end
|
115
|
+
|
116
|
+
def validate_theme
|
117
|
+
unless NAVBAR_THEME.keys.include?(@theme)
|
118
|
+
raise ArgumentError, "Il tema deve essere uno tra: #{NAVBAR_THEME.keys.join(', ')}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def validate_shadow
|
123
|
+
unless NAVBAR_SHADOW.keys.include?(@shadow)
|
124
|
+
raise ArgumentError, "L'ombra deve essere una tra: #{NAVBAR_SHADOW.keys.join(', ')}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def validate_sidebar_width
|
129
|
+
unless NAVBAR_SIDEBAR_WIDTH.keys.include?(@sidebar_width)
|
130
|
+
raise ArgumentError, "La larghezza della sidebar deve essere una tra: #{NAVBAR_SIDEBAR_WIDTH.keys.join(', ')}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
<aside class="<%= container_classes %>">
|
2
|
+
<!-- Header Section -->
|
3
|
+
<% if has_header? %>
|
4
|
+
<div class="px-6 py-4 border-b border-gray-200">
|
5
|
+
<% if header[:logo].present? %>
|
6
|
+
<div class="flex items-center">
|
7
|
+
<div class="flex-shrink-0">
|
8
|
+
<% if header[:logo].is_a?(Hash) %>
|
9
|
+
<%= bui_avatar(**header[:logo]) %>
|
10
|
+
<% else %>
|
11
|
+
<%= header[:logo].html_safe %>
|
12
|
+
<% end %>
|
13
|
+
</div>
|
14
|
+
<% if header[:title].present? %>
|
15
|
+
<div class="ml-3">
|
16
|
+
<h2 class="text-lg font-semibold text-gray-900"><%= header[:title] %></h2>
|
17
|
+
<% if header[:subtitle].present? %>
|
18
|
+
<p class="text-sm text-gray-500"><%= header[:subtitle] %></p>
|
19
|
+
<% end %>
|
20
|
+
</div>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
<% elsif header[:title].present? %>
|
24
|
+
<div>
|
25
|
+
<h2 class="text-lg font-semibold text-gray-900"><%= header[:title] %></h2>
|
26
|
+
<% if header[:subtitle].present? %>
|
27
|
+
<p class="text-sm text-gray-500"><%= header[:subtitle] %></p>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
<% end %>
|
31
|
+
</div>
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<!-- Navigation Section -->
|
35
|
+
<nav class="flex-1 px-4 py-6 space-y-6">
|
36
|
+
<% navigation_sections.each do |section| %>
|
37
|
+
<div class="space-y-2">
|
38
|
+
<!-- Section Title -->
|
39
|
+
<% if section[:title].present? %>
|
40
|
+
<% if section[:href] %>
|
41
|
+
<%= link_to section[:href], class: "px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider" do %>
|
42
|
+
<%= section[:title] %>
|
43
|
+
<% end %>
|
44
|
+
<% else %>
|
45
|
+
<h3 class="px-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
46
|
+
<%= section[:title] %>
|
47
|
+
</h3>
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
<!-- Navigation Items -->
|
52
|
+
<div class="space-y-1">
|
53
|
+
<% (section[:items] || []).each do |item| %>
|
54
|
+
<% if item[:type] == :collapsible && collapsible %>
|
55
|
+
<!-- Collapsible Section -->
|
56
|
+
<div>
|
57
|
+
<button
|
58
|
+
type="button"
|
59
|
+
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-collapse-target="#<%= item[:id] %>"
|
61
|
+
aria-expanded="<%= item[:expanded] || false %>"
|
62
|
+
>
|
63
|
+
<div class="flex items-center">
|
64
|
+
<% if item[:icon].present? %>
|
65
|
+
<span class="mr-3 text-gray-400 group-hover:text-gray-500">
|
66
|
+
<%= bui_icon(item[:icon], size: :medium) %>
|
67
|
+
</span>
|
68
|
+
<% end %>
|
69
|
+
<span><%= item[:label] %></span>
|
70
|
+
</div>
|
71
|
+
<span class="ml-3 transform transition-transform duration-150 <%= 'rotate-90' if item[:expanded] %>">
|
72
|
+
<%= bui_icon("chevron-right", size: :small) %>
|
73
|
+
</span>
|
74
|
+
</button>
|
75
|
+
|
76
|
+
<div
|
77
|
+
id="<%= item[:id] %>"
|
78
|
+
class="<%= item[:expanded] ? 'block' : 'hidden' %> mt-1 space-y-1"
|
79
|
+
>
|
80
|
+
<% (item[:children] || []).each do |child| %>
|
81
|
+
<% if child[:href].present? %>
|
82
|
+
<a
|
83
|
+
href="<%= child[:href] %>"
|
84
|
+
class="<%= (child[:active] || false) ? 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 bg-gray-100 text-gray-900' : 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 text-gray-700 hover:bg-gray-50 hover:text-gray-900' %> pl-11"
|
85
|
+
>
|
86
|
+
<% if child[:icon].present? %>
|
87
|
+
<span class="mr-3">
|
88
|
+
<%= bui_icon(child[:icon], size: :small) %>
|
89
|
+
</span>
|
90
|
+
<% end %>
|
91
|
+
<%= child[:label] %>
|
92
|
+
</a>
|
93
|
+
<% else %>
|
94
|
+
<div class="<%= (child[:active] || false) ? 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 bg-gray-100 text-gray-900' : 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 text-gray-700 hover:bg-gray-50 hover:text-gray-900' %> pl-11 cursor-default">
|
95
|
+
<% if child[:icon].present? %>
|
96
|
+
<span class="mr-3">
|
97
|
+
<%= bui_icon(child[:icon], size: :small) %>
|
98
|
+
</span>
|
99
|
+
<% end %>
|
100
|
+
<%= child[:label] %>
|
101
|
+
</div>
|
102
|
+
<% end %>
|
103
|
+
<% end %>
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
<% else %>
|
107
|
+
<!-- Regular Navigation Item -->
|
108
|
+
<% if item[:href].present? %>
|
109
|
+
<a
|
110
|
+
href="<%= item[:href] %>"
|
111
|
+
class="<%= (item[:active] || false) ? 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 bg-gray-100 text-gray-900' : 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 text-gray-700 hover:bg-gray-50 hover:text-gray-900' %>"
|
112
|
+
>
|
113
|
+
<% if item[:icon].present? %>
|
114
|
+
<span class="mr-3 text-gray-400 group-hover:text-gray-500">
|
115
|
+
<%= bui_icon(item[:icon], size: :medium) %>
|
116
|
+
</span>
|
117
|
+
<% end %>
|
118
|
+
<%= item[:label] %>
|
119
|
+
<% if item[:badge].present? %>
|
120
|
+
<span class="ml-auto inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
121
|
+
<%= item[:badge] %>
|
122
|
+
</span>
|
123
|
+
<% end %>
|
124
|
+
</a>
|
125
|
+
<% else %>
|
126
|
+
<div class="<%= (item[:active] || false) ? 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 bg-gray-100 text-gray-900' : 'group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors duration-150 text-gray-700 hover:bg-gray-50 hover:text-gray-900' %> cursor-default">
|
127
|
+
<% if item[:icon].present? %>
|
128
|
+
<span class="mr-3 text-gray-400">
|
129
|
+
<%= bui_icon(item[:icon], size: :medium) %>
|
130
|
+
</span>
|
131
|
+
<% end %>
|
132
|
+
<%= item[:label] %>
|
133
|
+
<% if item[:badge].present? %>
|
134
|
+
<span class="ml-auto inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
135
|
+
<%= item[:badge] %>
|
136
|
+
</span>
|
137
|
+
<% end %>
|
138
|
+
</div>
|
139
|
+
<% end %>
|
140
|
+
<% end %>
|
141
|
+
<% end %>
|
142
|
+
</div>
|
143
|
+
</div>
|
144
|
+
<% end %>
|
145
|
+
|
146
|
+
<%= content if content.present? %>
|
147
|
+
</nav>
|
148
|
+
|
149
|
+
<!-- Footer Section -->
|
150
|
+
<% if has_footer? %>
|
151
|
+
<div class="px-6 py-4 border-t border-gray-200">
|
152
|
+
<% if footer[:user_info].present? %>
|
153
|
+
<div class="flex items-center">
|
154
|
+
<% if footer[:user_info][:avatar].present? %>
|
155
|
+
<div class="flex-shrink-0">
|
156
|
+
<% if footer[:user_info][:avatar].is_a?(Hash) %>
|
157
|
+
<%= bui_avatar(**footer[:user_info][:avatar]) %>
|
158
|
+
<% else %>
|
159
|
+
<%= footer[:user_info][:avatar].html_safe %>
|
160
|
+
<% end %>
|
161
|
+
</div>
|
162
|
+
<% end %>
|
163
|
+
<div class="<%= footer[:user_info][:avatar].present? ? 'ml-3' : '' %>">
|
164
|
+
<% if footer[:user_info][:name].present? %>
|
165
|
+
<p class="text-sm font-medium text-gray-700">
|
166
|
+
<%= footer[:user_info][:name] %>
|
167
|
+
</p>
|
168
|
+
<% end %>
|
169
|
+
<% if footer[:user_info][:email].present? %>
|
170
|
+
<p class="text-xs text-gray-500">
|
171
|
+
<%= footer[:user_info][:email] %>
|
172
|
+
</p>
|
173
|
+
<% end %>
|
174
|
+
</div>
|
175
|
+
<% if footer[:user_info][:menu_button].present? %>
|
176
|
+
<div class="ml-auto">
|
177
|
+
<%= footer[:user_info][:menu_button] %>
|
178
|
+
</div>
|
179
|
+
<% end %>
|
180
|
+
</div>
|
181
|
+
<% end %>
|
182
|
+
|
183
|
+
<% if footer[:content].present? %>
|
184
|
+
<div class="<%= footer[:user_info].present? ? 'mt-4' : '' %>">
|
185
|
+
<%= footer[:content] %>
|
186
|
+
</div>
|
187
|
+
<% end %>
|
188
|
+
</div>
|
189
|
+
<% end %>
|
190
|
+
</aside>
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BetterUi
|
4
|
+
module Application
|
5
|
+
module Sidebar
|
6
|
+
class Component < ViewComponent::Base
|
7
|
+
# Include degli helper per utilizzare bui_icon e bui_avatar
|
8
|
+
include BetterUi::General::Components::Icon::IconHelper
|
9
|
+
include BetterUi::General::Components::Avatar::AvatarHelper
|
10
|
+
attr_reader :width, :position, :theme, :shadow, :border, :header, :footer, :navigation_sections, :collapsible, :classes
|
11
|
+
|
12
|
+
# Larghezze sidebar con classi Tailwind dirette
|
13
|
+
SIDEBAR_WIDTHS = {
|
14
|
+
sm: "w-48",
|
15
|
+
md: "w-64",
|
16
|
+
lg: "w-72",
|
17
|
+
xl: "w-80"
|
18
|
+
}
|
19
|
+
|
20
|
+
# Temi sidebar con classi Tailwind dirette
|
21
|
+
SIDEBAR_THEMES = {
|
22
|
+
default: "bg-white text-gray-900",
|
23
|
+
dark: "bg-gray-900 text-white",
|
24
|
+
light: "bg-white text-gray-900"
|
25
|
+
}
|
26
|
+
|
27
|
+
# Ombre sidebar con classi Tailwind dirette
|
28
|
+
SIDEBAR_SHADOWS = {
|
29
|
+
none: "",
|
30
|
+
sm: "shadow-sm",
|
31
|
+
md: "shadow-md",
|
32
|
+
lg: "shadow-lg",
|
33
|
+
xl: "shadow-xl"
|
34
|
+
}
|
35
|
+
|
36
|
+
# Bordi sidebar con classi Tailwind dirette
|
37
|
+
SIDEBAR_BORDERS = {
|
38
|
+
left: "border-r border-gray-200",
|
39
|
+
right: "border-l border-gray-200"
|
40
|
+
}
|
41
|
+
|
42
|
+
# @param width [Symbol] Larghezza della sidebar (:sm, :md, :lg, :xl), default :md (w-64)
|
43
|
+
# @param position [Symbol] Posizione della sidebar (:left, :right), default :left
|
44
|
+
# @param theme [Symbol] Tema colori (:default, :dark, :light), default :default
|
45
|
+
# @param shadow [Symbol] Tipo di ombra (:none, :sm, :md, :lg), default :lg
|
46
|
+
# @param border [Boolean] Se mostrare il bordo destro/sinistro, default true
|
47
|
+
# @param header [Hash] Configurazione header (logo, title, subtitle)
|
48
|
+
# @param footer [Hash] Configurazione footer (content, user_info)
|
49
|
+
# @param navigation_sections [Array] Array di sezioni di navigazione
|
50
|
+
# @param collapsible [Boolean] Se abilitare sezioni collassabili, default true
|
51
|
+
# @param classes [String] Classi CSS aggiuntive
|
52
|
+
def initialize(
|
53
|
+
width: :md,
|
54
|
+
position: :left,
|
55
|
+
theme: :default,
|
56
|
+
shadow: :lg,
|
57
|
+
border: true,
|
58
|
+
header: {},
|
59
|
+
footer: {},
|
60
|
+
navigation_sections: [],
|
61
|
+
collapsible: true,
|
62
|
+
classes: nil
|
63
|
+
)
|
64
|
+
@width = width.to_sym
|
65
|
+
@position = position.to_sym
|
66
|
+
@theme = theme.to_sym
|
67
|
+
@shadow = shadow.to_sym
|
68
|
+
@border = border
|
69
|
+
@header = header || {}
|
70
|
+
@footer = footer || {}
|
71
|
+
@navigation_sections = navigation_sections || []
|
72
|
+
@collapsible = collapsible
|
73
|
+
@classes = classes
|
74
|
+
end
|
75
|
+
|
76
|
+
def container_classes
|
77
|
+
base_classes = %w[fixed inset-y-0 z-50 flex flex-col overflow-y-auto]
|
78
|
+
|
79
|
+
# Posizione
|
80
|
+
base_classes << (position == :right ? "right-0" : "left-0")
|
81
|
+
|
82
|
+
# Larghezza
|
83
|
+
base_classes << width_class
|
84
|
+
|
85
|
+
# Tema
|
86
|
+
base_classes.concat(theme_classes)
|
87
|
+
|
88
|
+
# Shadow
|
89
|
+
base_classes << shadow_class if shadow != :none
|
90
|
+
|
91
|
+
# Border
|
92
|
+
base_classes << border_class if border
|
93
|
+
|
94
|
+
# Classi aggiuntive
|
95
|
+
base_classes << classes if classes.present?
|
96
|
+
|
97
|
+
base_classes.compact.join(" ")
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def has_header?
|
102
|
+
header.present? && (header[:title].present? || header[:logo].present?)
|
103
|
+
end
|
104
|
+
|
105
|
+
def has_footer?
|
106
|
+
footer.present? && (footer[:content].present? || footer[:user_info].present?)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def width_class
|
112
|
+
SIDEBAR_WIDTHS[@width] || SIDEBAR_WIDTHS[:md]
|
113
|
+
end
|
114
|
+
|
115
|
+
def theme_classes
|
116
|
+
(SIDEBAR_THEMES[@theme] || SIDEBAR_THEMES[:default]).split
|
117
|
+
end
|
118
|
+
|
119
|
+
def shadow_class
|
120
|
+
SIDEBAR_SHADOWS[@shadow] || SIDEBAR_SHADOWS[:none]
|
121
|
+
end
|
122
|
+
|
123
|
+
def border_class
|
124
|
+
SIDEBAR_BORDERS[@position] || SIDEBAR_BORDERS[:left]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|