better_ui 0.1.0 → 0.4.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -1
  3. data/app/assets/javascripts/better_ui/controllers/navbar_controller.js +138 -0
  4. data/app/assets/javascripts/better_ui/controllers/sidebar_controller.js +211 -0
  5. data/app/assets/javascripts/better_ui/controllers/toast_controller.js +161 -0
  6. data/app/assets/javascripts/better_ui/index.js +159 -0
  7. data/app/assets/javascripts/better_ui/toast_manager.js +77 -0
  8. data/app/assets/stylesheets/better_ui/application.css +25 -351
  9. data/app/components/better_ui/application/alert_component.html.erb +27 -0
  10. data/app/components/better_ui/application/alert_component.rb +196 -0
  11. data/app/components/better_ui/application/header_component.html.erb +88 -0
  12. data/app/components/better_ui/application/header_component.rb +188 -0
  13. data/app/components/better_ui/application/navbar_component.html.erb +294 -0
  14. data/app/components/better_ui/application/navbar_component.rb +249 -0
  15. data/app/components/better_ui/application/sidebar_component.html.erb +207 -0
  16. data/app/components/better_ui/application/sidebar_component.rb +318 -0
  17. data/app/components/better_ui/application/toast_component.html.erb +35 -0
  18. data/app/components/better_ui/application/toast_component.rb +188 -0
  19. data/app/components/better_ui/general/breadcrumb_component.html.erb +39 -0
  20. data/app/components/better_ui/general/breadcrumb_component.rb +132 -0
  21. data/app/components/better_ui/general/button_component.html.erb +34 -0
  22. data/app/components/better_ui/general/button_component.rb +193 -0
  23. data/app/components/better_ui/general/heading_component.html.erb +25 -0
  24. data/app/components/better_ui/general/heading_component.rb +142 -0
  25. data/app/components/better_ui/general/icon_component.html.erb +2 -0
  26. data/app/components/better_ui/general/icon_component.rb +101 -0
  27. data/app/components/better_ui/general/panel_component.html.erb +27 -0
  28. data/app/components/better_ui/general/panel_component.rb +97 -0
  29. data/app/components/better_ui/general/table_component.html.erb +37 -0
  30. data/app/components/better_ui/general/table_component.rb +141 -0
  31. data/app/components/better_ui/theme_helper.rb +169 -0
  32. data/app/controllers/better_ui/application_controller.rb +1 -0
  33. data/app/controllers/better_ui/docs_controller.rb +18 -25
  34. data/app/helpers/better_ui_application_helper.rb +99 -0
  35. data/app/views/layouts/component_preview.html.erb +32 -0
  36. data/config/initializers/lookbook.rb +23 -0
  37. data/config/routes.rb +6 -1
  38. data/lib/better_ui/engine.rb +24 -1
  39. data/lib/better_ui/version.rb +1 -1
  40. metadata +103 -7
  41. data/app/helpers/better_ui/application_helper.rb +0 -183
  42. data/app/views/better_ui/docs/component.html.erb +0 -365
  43. data/app/views/better_ui/docs/index.html.erb +0 -100
  44. data/app/views/better_ui/docs/show.html.erb +0 -60
  45. data/app/views/layouts/better_ui/application.html.erb +0 -135
@@ -0,0 +1,196 @@
1
+ module BetterUi
2
+ module Application
3
+ class AlertComponent < ViewComponent::Base
4
+ attr_reader :title, :message, :variant, :icon, :dismissible, :classes, :data, :icon_position, :outline
5
+
6
+ # Varianti di colore disponibili
7
+ VARIANTS = {
8
+ primary: {
9
+ bg: "bg-orange-50",
10
+ border: "border-orange-300",
11
+ title: "text-orange-800",
12
+ text: "text-orange-700",
13
+ icon: "text-orange-500",
14
+ close: "text-orange-500 hover:bg-orange-100",
15
+ outline_bg: "bg-white",
16
+ outline_text: "text-orange-700"
17
+ },
18
+ info: {
19
+ bg: "bg-blue-50",
20
+ border: "border-blue-300",
21
+ title: "text-blue-800",
22
+ text: "text-blue-700",
23
+ icon: "text-blue-500",
24
+ close: "text-blue-500 hover:bg-blue-100",
25
+ outline_bg: "bg-white",
26
+ outline_text: "text-blue-700"
27
+ },
28
+ success: {
29
+ bg: "bg-green-50",
30
+ border: "border-green-300",
31
+ title: "text-green-800",
32
+ text: "text-green-700",
33
+ icon: "text-green-500",
34
+ close: "text-green-500 hover:bg-green-100",
35
+ outline_bg: "bg-white",
36
+ outline_text: "text-green-700"
37
+ },
38
+ warning: {
39
+ bg: "bg-yellow-50",
40
+ border: "border-yellow-300",
41
+ title: "text-yellow-800",
42
+ text: "text-yellow-700",
43
+ icon: "text-yellow-500",
44
+ close: "text-yellow-500 hover:bg-yellow-100",
45
+ outline_bg: "bg-white",
46
+ outline_text: "text-yellow-700"
47
+ },
48
+ danger: {
49
+ bg: "bg-red-50",
50
+ border: "border-red-300",
51
+ title: "text-red-800",
52
+ text: "text-red-700",
53
+ icon: "text-red-500",
54
+ close: "text-red-500 hover:bg-red-100",
55
+ outline_bg: "bg-white",
56
+ outline_text: "text-red-700"
57
+ },
58
+ dark: {
59
+ bg: "bg-gray-800",
60
+ border: "border-gray-700",
61
+ title: "text-white",
62
+ text: "text-gray-300",
63
+ icon: "text-gray-400",
64
+ close: "text-gray-400 hover:bg-gray-700",
65
+ outline_bg: "bg-white",
66
+ outline_text: "text-gray-800"
67
+ },
68
+ simple: {
69
+ bg: "bg-white",
70
+ border: "border-gray-200",
71
+ title: "text-gray-800 font-semibold",
72
+ text: "text-gray-600",
73
+ icon: "text-gray-500",
74
+ close: "text-gray-500 hover:bg-gray-100",
75
+ outline_bg: "bg-white",
76
+ outline_text: "text-gray-700"
77
+ }
78
+ }
79
+
80
+ # Icone predefinite per ciascuna variante
81
+ DEFAULT_ICONS = {
82
+ primary: "bell",
83
+ info: "info-circle",
84
+ success: "check-circle",
85
+ warning: "exclamation-triangle",
86
+ danger: "exclamation-circle",
87
+ dark: "shield-exclamation",
88
+ simple: "info-circle"
89
+ }
90
+
91
+ # Posizioni possibili per le icone
92
+ ICON_POSITIONS = [:left, :top]
93
+
94
+ # Inizializzazione del componente
95
+ def initialize(
96
+ title: nil,
97
+ message: nil,
98
+ variant: :simple,
99
+ icon: nil,
100
+ dismissible: false,
101
+ classes: nil,
102
+ data: {},
103
+ icon_position: :left,
104
+ outline: false
105
+ )
106
+ @title = title
107
+ @message = message
108
+ @variant = variant.to_sym
109
+ @icon = icon
110
+ @dismissible = dismissible
111
+ @classes = classes
112
+ @data = data
113
+ @icon_position = icon_position.to_sym
114
+ @outline = outline
115
+ end
116
+
117
+ # Genera l'icona in base alla variante se non specificata
118
+ def effective_icon
119
+ return @icon if @icon.present?
120
+ DEFAULT_ICONS[@variant]
121
+ end
122
+
123
+ # Genera le classi per il container
124
+ def container_classes
125
+ styles = VARIANTS.fetch(@variant, VARIANTS[:simple])
126
+
127
+ base_classes = ["p-4 mb-4 flex"]
128
+
129
+ if @variant == :simple
130
+ base_classes << "border rounded-md"
131
+ else
132
+ base_classes << "rounded-lg border"
133
+ end
134
+
135
+ [
136
+ *base_classes,
137
+ @outline ? styles[:outline_bg] : styles[:bg],
138
+ styles[:border],
139
+ @icon_position == :top ? "flex-col" : "items-start",
140
+ @classes
141
+ ].compact.join(" ")
142
+ end
143
+
144
+ # Genera le classi per il titolo
145
+ def title_classes
146
+ styles = VARIANTS.fetch(@variant, VARIANTS[:simple])
147
+
148
+ [
149
+ @variant == :simple ? "font-semibold" : "font-medium",
150
+ @outline ? styles[:outline_text] : styles[:title]
151
+ ].compact.join(" ")
152
+ end
153
+
154
+ # Genera le classi per il messaggio
155
+ def message_classes
156
+ styles = VARIANTS.fetch(@variant, VARIANTS[:simple])
157
+
158
+ [
159
+ "mt-1",
160
+ @outline ? styles[:outline_text] : styles[:text]
161
+ ].compact.join(" ")
162
+ end
163
+
164
+ # Genera le classi per l'icona
165
+ def icon_classes
166
+ styles = VARIANTS.fetch(@variant, VARIANTS[:simple])
167
+
168
+ [
169
+ "flex-shrink-0",
170
+ @icon_position == :left ? "mr-3 mt-0.5" : "mb-3",
171
+ styles[:icon]
172
+ ].compact.join(" ")
173
+ end
174
+
175
+ # Genera le classi per il pulsante di chiusura
176
+ def close_button_classes
177
+ styles = VARIANTS.fetch(@variant, VARIANTS[:simple])
178
+
179
+ [
180
+ "ml-auto -mr-1.5 -mt-1.5 inline-flex h-8 w-8 rounded-lg p-1.5 focus:ring-2 focus:ring-gray-400",
181
+ styles[:close]
182
+ ].compact.join(" ")
183
+ end
184
+
185
+ # Genera le classi per il contenuto
186
+ def content_classes
187
+ "flex-1"
188
+ end
189
+
190
+ # Verifica se il componente deve essere reso
191
+ def render?
192
+ @title.present? || @message.present? || content.present?
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,88 @@
1
+ <header class="<%= header_classes %>" <%= @data&.map { |k, v| "data-#{k}=\"#{v}\"" }&.join(' ')&.html_safe %>>
2
+ <div class="<%= container_class %>">
3
+ <div class="md:flex md:justify-between md:items-start">
4
+ <div class="min-w-0 flex-1">
5
+ <% if has_breadcrumbs? %>
6
+ <nav class="<%= breadcrumb_container_classes %>" aria-label="Breadcrumb">
7
+ <ol class="flex items-center">
8
+ <% @breadcrumbs.each_with_index do |breadcrumb, index| %>
9
+ <li class="flex items-center">
10
+ <% if index > 0 %>
11
+ <span class="<%= breadcrumb_divider_classes %>" aria-hidden="true">
12
+ <% if variant == :modern %>
13
+ <svg class="h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
14
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
15
+ </svg>
16
+ <% else %>
17
+ /
18
+ <% end %>
19
+ </span>
20
+ <% end %>
21
+ <% is_last = index == @breadcrumbs.length - 1 %>
22
+ <% if is_last || !breadcrumb[:url].present? %>
23
+ <span class="<%= breadcrumb_link_classes(is_last) %>" <%= is_last ? 'aria-current="page"' : '' %>>
24
+ <%= breadcrumb[:text] %>
25
+ </span>
26
+ <% else %>
27
+ <a href="<%= breadcrumb[:url] %>" class="<%= breadcrumb_link_classes %>">
28
+ <%= breadcrumb[:text] %>
29
+ </a>
30
+ <% end %>
31
+ </li>
32
+ <% end %>
33
+ </ol>
34
+ </nav>
35
+ <% end %>
36
+
37
+ <div class="flex items-center">
38
+ <% if @title.is_a?(Hash) && @title[:icon].present? %>
39
+ <div class="mr-3 bg-gray-100 rounded-md p-2 flex-shrink-0">
40
+ <% if @title[:icon] == "document" %>
41
+ <svg class="h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
42
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
43
+ </svg>
44
+ <% elsif @title[:icon] == "users" %>
45
+ <svg class="h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
47
+ </svg>
48
+ <% elsif @title[:icon] == "settings" || @title[:icon] == "gear" %>
49
+ <svg class="h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
50
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
51
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
52
+ </svg>
53
+ <% elsif @title[:icon] == "dashboard" %>
54
+ <svg class="h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
55
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
56
+ </svg>
57
+ <% else %>
58
+ <%= render BetterUi::General::IconComponent.new(name: @title[:icon], classes: "h-5 w-5 text-gray-500") %>
59
+ <% end %>
60
+ </div>
61
+ <h1 class="<%= title_classes %>"><%= @title[:text] %></h1>
62
+ <% else %>
63
+ <h1 class="<%= title_classes %>"><%= @title %></h1>
64
+ <% end %>
65
+ </div>
66
+ <% if @subtitle.present? %>
67
+ <p class="<%= subtitle_classes %>">
68
+ <%= @subtitle %>
69
+ </p>
70
+ <% end %>
71
+ </div>
72
+
73
+ <% if action_items.any? %>
74
+ <div class="<%= actions_container_classes %>">
75
+ <% action_items.each do |action| %>
76
+ <div class="ml-2">
77
+ <% if action.is_a?(Hash) && action[:content].present? %>
78
+ <%= action[:content].html_safe %>
79
+ <% elsif action.is_a?(String) %>
80
+ <%= action.html_safe %>
81
+ <% end %>
82
+ </div>
83
+ <% end %>
84
+ </div>
85
+ <% end %>
86
+ </div>
87
+ </div>
88
+ </header>
@@ -0,0 +1,188 @@
1
+ module BetterUi
2
+ module Application
3
+ class HeaderComponent < ViewComponent::Base
4
+ attr_reader :title, :subtitle, :breadcrumbs, :actions, :variant, :fixed, :container_class, :classes, :data, :show_breadcrumbs
5
+
6
+ # Varianti di colore disponibili
7
+ VARIANTS = {
8
+ light: {
9
+ bg: "bg-white border-gray-200",
10
+ text: "text-gray-700",
11
+ title: "text-gray-900",
12
+ subtitle: "text-gray-500",
13
+ border: "border-gray-200",
14
+ breadcrumb_text: "text-gray-500",
15
+ breadcrumb_hover: "hover:text-gray-700",
16
+ breadcrumb_active: "text-gray-900",
17
+ breadcrumb_divider: "text-gray-400"
18
+ },
19
+ dark: {
20
+ bg: "bg-gray-800 border-gray-700",
21
+ text: "text-gray-200",
22
+ title: "text-white",
23
+ subtitle: "text-gray-400",
24
+ border: "border-gray-700",
25
+ breadcrumb_text: "text-gray-400",
26
+ breadcrumb_hover: "hover:text-white",
27
+ breadcrumb_active: "text-white",
28
+ breadcrumb_divider: "text-gray-500"
29
+ },
30
+ primary: {
31
+ bg: "bg-orange-600",
32
+ text: "text-white",
33
+ title: "text-white",
34
+ subtitle: "text-orange-100",
35
+ border: "border-orange-500",
36
+ breadcrumb_text: "text-orange-200",
37
+ breadcrumb_hover: "hover:text-white",
38
+ breadcrumb_active: "text-white",
39
+ breadcrumb_divider: "text-orange-300"
40
+ },
41
+ transparent: {
42
+ bg: "bg-transparent",
43
+ text: "text-gray-700",
44
+ title: "text-gray-900",
45
+ subtitle: "text-gray-500",
46
+ border: "border-gray-200",
47
+ breadcrumb_text: "text-gray-500",
48
+ breadcrumb_hover: "hover:text-gray-700",
49
+ breadcrumb_active: "text-gray-900",
50
+ breadcrumb_divider: "text-gray-400"
51
+ },
52
+ modern: {
53
+ bg: "bg-white",
54
+ text: "text-gray-700",
55
+ title: "text-gray-900",
56
+ subtitle: "text-gray-500",
57
+ border: "border-gray-100",
58
+ breadcrumb_text: "text-gray-500",
59
+ breadcrumb_hover: "hover:text-gray-700",
60
+ breadcrumb_active: "text-gray-900",
61
+ breadcrumb_divider: "text-gray-400"
62
+ }
63
+ }
64
+
65
+ # Opzioni per la posizione fissa
66
+ FIXED_POSITIONS = {
67
+ top: "sticky top-0 z-40",
68
+ bottom: "sticky bottom-0 z-40"
69
+ }
70
+
71
+ # Inizializzazione del componente
72
+ def initialize(
73
+ title:,
74
+ subtitle: nil,
75
+ breadcrumbs: [],
76
+ actions: [],
77
+ variant: :modern,
78
+ fixed: nil,
79
+ container_class: "container mx-auto px-4",
80
+ classes: nil,
81
+ data: {},
82
+ show_breadcrumbs: true
83
+ )
84
+ @title = title
85
+ @subtitle = subtitle
86
+ @breadcrumbs = breadcrumbs || []
87
+ @actions = actions || []
88
+ @variant = variant.to_sym
89
+ @fixed = fixed.to_sym if fixed
90
+ @container_class = container_class
91
+ @classes = classes
92
+ @data = data || {}
93
+ @show_breadcrumbs = show_breadcrumbs
94
+ end
95
+
96
+ # Genera le classi per il container del header
97
+ def header_classes
98
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
99
+ position_class = @fixed.present? ? FIXED_POSITIONS[@fixed] : nil
100
+
101
+ cls = [
102
+ "w-full py-4",
103
+ styles[:bg],
104
+ position_class,
105
+ @classes
106
+ ]
107
+
108
+ # Aggiungi il bordo inferiore solo se non è trasparente
109
+ cls << "border-b" unless @variant == :transparent
110
+
111
+ cls.compact.join(" ")
112
+ end
113
+
114
+ # Genera classi per il titolo
115
+ def title_classes
116
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
117
+
118
+ [
119
+ "text-xl font-medium leading-6",
120
+ styles[:title]
121
+ ].join(" ")
122
+ end
123
+
124
+ # Genera classi per il sottotitolo
125
+ def subtitle_classes
126
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
127
+
128
+ [
129
+ "mt-1 text-sm",
130
+ styles[:subtitle]
131
+ ].join(" ")
132
+ end
133
+
134
+ # Genera classi per il breadcrumb
135
+ def breadcrumb_container_classes
136
+ "flex mb-3"
137
+ end
138
+
139
+ # Genera classi per i link del breadcrumb
140
+ def breadcrumb_link_classes(active = false)
141
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
142
+
143
+ if active
144
+ [
145
+ "text-sm font-medium",
146
+ styles[:breadcrumb_active]
147
+ ].join(" ")
148
+ else
149
+ [
150
+ "text-sm font-medium",
151
+ styles[:breadcrumb_text],
152
+ styles[:breadcrumb_hover]
153
+ ].join(" ")
154
+ end
155
+ end
156
+
157
+ # Genera classi per il divisore del breadcrumb
158
+ def breadcrumb_divider_classes
159
+ styles = VARIANTS.fetch(@variant, VARIANTS[:modern])
160
+
161
+ [
162
+ "mx-2 text-sm",
163
+ styles[:breadcrumb_divider]
164
+ ].join(" ")
165
+ end
166
+
167
+ # Genera classi per il contenitore delle azioni
168
+ def actions_container_classes
169
+ "mt-4 md:mt-0 flex flex-shrink-0 md:ml-4"
170
+ end
171
+
172
+ # Verifica se il componente deve essere reso
173
+ def render?
174
+ true
175
+ end
176
+
177
+ # Ritorna la lista delle azioni
178
+ def action_items
179
+ @actions
180
+ end
181
+
182
+ # Ritorna i breadcrumbs
183
+ def has_breadcrumbs?
184
+ @show_breadcrumbs && @breadcrumbs.present? && @breadcrumbs.any?
185
+ end
186
+ end
187
+ end
188
+ end