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,142 @@
1
+ module BetterUi
2
+ module General
3
+ class HeadingComponent < ViewComponent::Base
4
+ attr_reader :text, :level, :variant, :size, :align, :classes, :icon, :subtitle, :with_divider
5
+
6
+ # Varianti di colore disponibili
7
+ VARIANTS = {
8
+ default: {
9
+ heading: "text-gray-900",
10
+ subtitle: "text-gray-600",
11
+ divider: "border-gray-200"
12
+ },
13
+ primary: {
14
+ heading: "text-orange-700",
15
+ subtitle: "text-orange-500",
16
+ divider: "border-orange-300"
17
+ },
18
+ success: {
19
+ heading: "text-green-700",
20
+ subtitle: "text-green-500",
21
+ divider: "border-green-300"
22
+ },
23
+ warning: {
24
+ heading: "text-yellow-700",
25
+ subtitle: "text-yellow-500",
26
+ divider: "border-yellow-300"
27
+ },
28
+ danger: {
29
+ heading: "text-red-700",
30
+ subtitle: "text-red-500",
31
+ divider: "border-red-300"
32
+ },
33
+ info: {
34
+ heading: "text-blue-700",
35
+ subtitle: "text-blue-500",
36
+ divider: "border-blue-300"
37
+ },
38
+ light: {
39
+ heading: "text-gray-100",
40
+ subtitle: "text-gray-300",
41
+ divider: "border-gray-700"
42
+ }
43
+ }
44
+
45
+ # Dimensioni disponibili
46
+ SIZES = {
47
+ xs: {
48
+ heading: "text-lg",
49
+ subtitle: "text-sm"
50
+ },
51
+ sm: {
52
+ heading: "text-xl",
53
+ subtitle: "text-base"
54
+ },
55
+ md: {
56
+ heading: "text-2xl",
57
+ subtitle: "text-lg"
58
+ },
59
+ lg: {
60
+ heading: "text-3xl",
61
+ subtitle: "text-xl"
62
+ },
63
+ xl: {
64
+ heading: "text-4xl",
65
+ subtitle: "text-2xl"
66
+ },
67
+ xxl: {
68
+ heading: "text-5xl",
69
+ subtitle: "text-3xl"
70
+ }
71
+ }
72
+
73
+ # Allineamenti disponibili
74
+ ALIGNMENTS = {
75
+ left: "text-left",
76
+ center: "text-center",
77
+ right: "text-right"
78
+ }
79
+
80
+ # Inizializzazione del componente
81
+ def initialize(
82
+ text: nil,
83
+ level: 2,
84
+ variant: :default,
85
+ size: :md,
86
+ align: :left,
87
+ classes: nil,
88
+ icon: nil,
89
+ subtitle: nil,
90
+ with_divider: false
91
+ )
92
+ @text = text
93
+ @level = level.to_i.clamp(1, 6)
94
+ @variant = variant.to_sym
95
+ @size = size.to_sym
96
+ @align = align.to_sym
97
+ @classes = classes
98
+ @icon = icon
99
+ @subtitle = subtitle
100
+ @with_divider = with_divider
101
+ end
102
+
103
+ # Genera le classi per l'heading
104
+ def heading_classes
105
+ [
106
+ VARIANTS.fetch(@variant, VARIANTS[:default])[:heading],
107
+ SIZES.fetch(@size, SIZES[:md])[:heading],
108
+ ALIGNMENTS.fetch(@align, ALIGNMENTS[:left]),
109
+ "font-bold",
110
+ @classes
111
+ ].compact.join(" ")
112
+ end
113
+
114
+ # Genera le classi per il sottotitolo
115
+ def subtitle_classes
116
+ [
117
+ VARIANTS.fetch(@variant, VARIANTS[:default])[:subtitle],
118
+ SIZES.fetch(@size, SIZES[:md])[:subtitle],
119
+ ALIGNMENTS.fetch(@align, ALIGNMENTS[:left]),
120
+ "mt-1"
121
+ ].compact.join(" ")
122
+ end
123
+
124
+ # Genera le classi per il divisore
125
+ def divider_classes
126
+ [
127
+ "border-t",
128
+ "mt-2",
129
+ VARIANTS.fetch(@variant, VARIANTS[:default])[:divider]
130
+ ].compact.join(" ")
131
+ end
132
+
133
+ # Genera le classi per il container
134
+ def container_classes
135
+ [
136
+ "mb-4",
137
+ @with_divider ? "pb-2" : ""
138
+ ].compact.join(" ")
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,2 @@
1
+ <%# Template per l'icona %>
2
+ <i class="<%= combined_classes %>" aria-hidden="true"></i>
@@ -0,0 +1,101 @@
1
+ module BetterUi
2
+ module General
3
+ class IconComponent < ViewComponent::Base
4
+ # Dimensioni disponibili
5
+ SIZE_CLASSES = {
6
+ xs: "fa-xs",
7
+ sm: "fa-sm",
8
+ md: "", # Default di Font Awesome
9
+ lg: "fa-lg",
10
+ xl: "fa-xl",
11
+ "2xl": "fa-2xl"
12
+ }
13
+
14
+ # Stili disponibili
15
+ STYLE_CLASSES = {
16
+ solid: "fas",
17
+ regular: "far",
18
+ light: "fal",
19
+ brands: "fab",
20
+ duotone: "fad"
21
+ }
22
+
23
+ # Inizializzazione del componente
24
+ def initialize(
25
+ name:,
26
+ size: :md,
27
+ style: :solid,
28
+ fixed_width: false,
29
+ spin: false,
30
+ pulse: false,
31
+ border: false,
32
+ flip: nil,
33
+ rotation: nil,
34
+ classes: nil
35
+ )
36
+ @name = name.to_s.gsub('_', '-') # Convertiamo da snake_case a kebab-case per Font Awesome
37
+ @size = size.to_sym
38
+ @style = style.to_sym
39
+ @fixed_width = fixed_width
40
+ @spin = spin
41
+ @pulse = pulse
42
+ @border = border
43
+ @flip = flip
44
+ @rotation = rotation
45
+ @classes = classes
46
+ end
47
+
48
+ # Classe CSS per lo stile dell'icona
49
+ def style_class
50
+ STYLE_CLASSES[@style] || STYLE_CLASSES[:solid]
51
+ end
52
+
53
+ # Classe CSS per la dimensione
54
+ def size_class
55
+ SIZE_CLASSES[@size] || SIZE_CLASSES[:md]
56
+ end
57
+
58
+ # Classe per rotazione
59
+ def rotation_class
60
+ return "" unless @rotation
61
+ "fa-rotate-#{@rotation}"
62
+ end
63
+
64
+ # Classe per rovesciamento
65
+ def flip_class
66
+ return "" unless @flip
67
+ "fa-flip-#{@flip}"
68
+ end
69
+
70
+ # Classi per animazioni
71
+ def animation_classes
72
+ classes = []
73
+ classes << "fa-spin" if @spin
74
+ classes << "fa-pulse" if @pulse
75
+ classes.join(" ")
76
+ end
77
+
78
+ # Classi per caratteristiche aggiuntive
79
+ def feature_classes
80
+ classes = []
81
+ classes << "fa-fw" if @fixed_width
82
+ classes << "fa-border" if @border
83
+ classes.join(" ")
84
+ end
85
+
86
+ # Combinazione di tutte le classi
87
+ def combined_classes
88
+ [
89
+ style_class,
90
+ "fa-#{@name}",
91
+ size_class,
92
+ rotation_class,
93
+ flip_class,
94
+ animation_classes,
95
+ feature_classes,
96
+ @classes
97
+ ].reject(&:blank?).join(" ")
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,27 @@
1
+ <div class="<%= panel_classes %>">
2
+ <% if @header.present? %>
3
+ <div class="<%= header_classes %>">
4
+ <%= raw @header %>
5
+ </div>
6
+ <% elsif @title.present? %>
7
+ <div class="<%= header_classes %>">
8
+ <div class="<%= title_classes %>"><%= @title %></div>
9
+ </div>
10
+ <% end %>
11
+
12
+ <% if @body.present? %>
13
+ <div class="<%= body_classes %>">
14
+ <%= raw @body %>
15
+ </div>
16
+ <% elsif content.present? %>
17
+ <div class="<%= body_classes %>">
18
+ <%= content %>
19
+ </div>
20
+ <% end %>
21
+
22
+ <% if @footer.present? %>
23
+ <div class="<%= footer_classes %>">
24
+ <%= raw @footer %>
25
+ </div>
26
+ <% end %>
27
+ </div>
@@ -0,0 +1,97 @@
1
+ module BetterUi
2
+ module General
3
+ class PanelComponent < ViewComponent::Base
4
+ attr_reader :header, :footer, :body, :title, :padding, :variant
5
+
6
+ PADDING_OPTIONS = {
7
+ none: '',
8
+ small: 'p-2',
9
+ medium: 'p-4',
10
+ large: 'p-6'
11
+ }.freeze
12
+
13
+ def initialize(title: nil, body: nil, header: nil, footer: nil, padding: :medium, variant: :default)
14
+ @title = title
15
+ @body = body
16
+ @header = header
17
+ @footer = footer
18
+ @padding = padding.to_sym
19
+ @variant = variant.to_sym
20
+ end
21
+
22
+ def panel_classes
23
+ ThemeHelper.generate_component_classes(:panel, @variant)
24
+ end
25
+
26
+ def header_classes
27
+ [
28
+ 'panel-header',
29
+ ThemeHelper::LAYOUT_STYLES[:panel][:header],
30
+ header_color_classes,
31
+ PADDING_OPTIONS.fetch(@padding, PADDING_OPTIONS[:medium])
32
+ ].compact.join(' ')
33
+ end
34
+
35
+ def body_classes
36
+ [
37
+ 'panel-body',
38
+ ThemeHelper::LAYOUT_STYLES[:panel][:body],
39
+ 'overflow-x-auto break-words',
40
+ PADDING_OPTIONS.fetch(@padding, PADDING_OPTIONS[:medium])
41
+ ].compact.join(' ')
42
+ end
43
+
44
+ def footer_classes
45
+ [
46
+ 'panel-footer',
47
+ ThemeHelper::LAYOUT_STYLES[:panel][:footer],
48
+ footer_color_classes,
49
+ 'overflow-x-auto break-words',
50
+ PADDING_OPTIONS.fetch(@padding, PADDING_OPTIONS[:medium])
51
+ ].compact.join(' ')
52
+ end
53
+
54
+ def title_classes
55
+ 'text-lg font-medium'
56
+ end
57
+
58
+ def header_color_classes
59
+ case @variant
60
+ when :primary
61
+ 'bg-orange-50 text-orange-700'
62
+ when :success
63
+ 'bg-green-50 text-green-700'
64
+ when :warning
65
+ 'bg-amber-50 text-amber-700'
66
+ when :danger
67
+ 'bg-red-50 text-red-700'
68
+ when :info
69
+ 'bg-blue-50 text-blue-700'
70
+ else
71
+ 'bg-gray-50 text-gray-700'
72
+ end
73
+ end
74
+
75
+ def footer_color_classes
76
+ case @variant
77
+ when :primary
78
+ 'bg-orange-50 text-orange-600'
79
+ when :success
80
+ 'bg-green-50 text-green-600'
81
+ when :warning
82
+ 'bg-amber-50 text-amber-600'
83
+ when :danger
84
+ 'bg-red-50 text-red-600'
85
+ when :info
86
+ 'bg-blue-50 text-blue-600'
87
+ else
88
+ 'bg-gray-50 text-gray-600'
89
+ end
90
+ end
91
+
92
+ def render?
93
+ @body.present? || @header.present? || @footer.present? || content.present?
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,37 @@
1
+ <div class="<%= table_container_classes %>">
2
+ <table class="<%= table_classes %>">
3
+ <% if caption.present? %>
4
+ <caption class="<%= caption_classes %>">
5
+ <%= caption %>
6
+ </caption>
7
+ <% end %>
8
+
9
+ <thead class="<%= thead_classes %>">
10
+ <tr>
11
+ <% headers_for_display.each do |header| %>
12
+ <th scope="col" class="<%= th_classes %>">
13
+ <%= header.to_s.humanize %>
14
+ </th>
15
+ <% end %>
16
+ </tr>
17
+ </thead>
18
+
19
+ <tbody class="<%= tbody_classes %>">
20
+ <% data.each_with_index do |row, index| %>
21
+ <tr class="<%= tr_classes(index) %>">
22
+ <% headers_for_display.each do |header| %>
23
+ <td class="<%= td_classes %>">
24
+ <% if row.is_a?(Hash) %>
25
+ <%= row[header.to_s] || row[header.to_sym] %>
26
+ <% elsif row.respond_to?(header.to_sym) %>
27
+ <%= row.send(header.to_sym) %>
28
+ <% else %>
29
+
30
+ <% end %>
31
+ </td>
32
+ <% end %>
33
+ </tr>
34
+ <% end %>
35
+ </tbody>
36
+ </table>
37
+ </div>
@@ -0,0 +1,141 @@
1
+ module BetterUi
2
+ module General
3
+ class TableComponent < ViewComponent::Base
4
+ attr_reader :data, :headers, :caption, :striped, :hoverable, :bordered, :compact, :classes, :variant
5
+
6
+ def initialize(data:, headers: nil, caption: nil, striped: false, hoverable: false, bordered: true, compact: false, classes: nil, variant: :default)
7
+ @data = data || []
8
+ @headers = headers
9
+ @caption = caption
10
+ @striped = striped
11
+ @hoverable = hoverable
12
+ @bordered = bordered
13
+ @compact = compact
14
+ @classes = classes
15
+ @variant = variant.to_sym
16
+ end
17
+
18
+ def table_classes
19
+ ThemeHelper.generate_component_classes(:table, @variant, { bordered: @bordered, classes: @classes })
20
+ end
21
+
22
+ def table_container_classes
23
+ [
24
+ ThemeHelper::LAYOUT_STYLES[:table][:container],
25
+ get_border_color
26
+ ].compact.join(' ')
27
+ end
28
+
29
+ def caption_classes
30
+ [
31
+ 'px-4 py-2',
32
+ 'text-sm font-medium text-left',
33
+ caption_color_classes,
34
+ @bordered ? "border-b #{get_border_color}" : nil
35
+ ].compact.join(' ')
36
+ end
37
+
38
+ def thead_classes
39
+ ThemeHelper::LAYOUT_STYLES[:table][:header]
40
+ end
41
+
42
+ def tbody_classes
43
+ @striped ? ThemeHelper::LAYOUT_STYLES[:table][:row][:striped] : nil
44
+ end
45
+
46
+ def tr_classes(index)
47
+ [
48
+ @hoverable ? ThemeHelper::LAYOUT_STYLES[:table][:row][:hover] : nil,
49
+ @striped ? nil : (index.odd? ? 'bg-gray-50' : nil)
50
+ ].compact.join(' ')
51
+ end
52
+
53
+ def th_classes
54
+ [
55
+ @compact ? 'px-2 py-1' : 'px-4 py-3',
56
+ 'text-left text-xs font-medium uppercase tracking-wider',
57
+ th_color_classes,
58
+ @bordered ? "border #{get_border_color}" : nil
59
+ ].compact.join(' ')
60
+ end
61
+
62
+ def td_classes
63
+ [
64
+ @compact ? 'px-2 py-1' : 'px-4 py-3',
65
+ @bordered ? "border #{get_border_color}" : nil,
66
+ 'text-sm'
67
+ ].compact.join(' ')
68
+ end
69
+
70
+ def get_border_color
71
+ case @variant
72
+ when :primary
73
+ 'border-orange-200'
74
+ when :success
75
+ 'border-green-200'
76
+ when :warning
77
+ 'border-amber-200'
78
+ when :danger
79
+ 'border-red-200'
80
+ when :info
81
+ 'border-blue-200'
82
+ else
83
+ 'border-gray-200'
84
+ end
85
+ end
86
+
87
+ def caption_color_classes
88
+ case @variant
89
+ when :primary
90
+ 'bg-orange-50 text-orange-700'
91
+ when :success
92
+ 'bg-green-50 text-green-700'
93
+ when :warning
94
+ 'bg-amber-50 text-amber-700'
95
+ when :danger
96
+ 'bg-red-50 text-red-700'
97
+ when :info
98
+ 'bg-blue-50 text-blue-700'
99
+ else
100
+ 'bg-gray-50 text-gray-700'
101
+ end
102
+ end
103
+
104
+ def th_color_classes
105
+ case @variant
106
+ when :primary
107
+ 'text-orange-700'
108
+ when :success
109
+ 'text-green-700'
110
+ when :warning
111
+ 'text-amber-700'
112
+ when :danger
113
+ 'text-red-700'
114
+ when :info
115
+ 'text-blue-700'
116
+ else
117
+ 'text-gray-700'
118
+ end
119
+ end
120
+
121
+ def headers_for_display
122
+ return @headers if @headers.present?
123
+ return [] if @data.empty?
124
+
125
+ # Se non sono stati forniti headers, li derivo dalle chiavi del primo elemento
126
+ first_item = @data.first
127
+ if first_item.is_a?(Hash)
128
+ first_item.keys
129
+ elsif first_item.respond_to?(:attributes)
130
+ first_item.attributes.keys - ['id', 'created_at', 'updated_at']
131
+ else
132
+ []
133
+ end
134
+ end
135
+
136
+ def render?
137
+ @data.present?
138
+ end
139
+ end
140
+ end
141
+ end