maquina-components 0.1.1 → 0.2.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +410 -13
  3. data/app/assets/images/maquina.svg +1 -0
  4. data/app/assets/stylesheets/alert.css +143 -0
  5. data/app/assets/stylesheets/badge.css +145 -0
  6. data/app/assets/stylesheets/breadcrumbs.css +163 -0
  7. data/app/assets/stylesheets/card.css +128 -0
  8. data/app/assets/stylesheets/dropdown_menu.css +248 -0
  9. data/app/assets/stylesheets/empty.css +133 -0
  10. data/app/assets/stylesheets/form.css +617 -0
  11. data/app/assets/stylesheets/header.css +61 -0
  12. data/app/assets/stylesheets/maquina_components.css +178 -0
  13. data/app/assets/stylesheets/pagination.css +154 -0
  14. data/app/assets/stylesheets/sidebar.css +477 -0
  15. data/app/assets/stylesheets/table.css +205 -0
  16. data/app/assets/stylesheets/toggle_group.css +151 -0
  17. data/app/assets/tailwind/maquina_components_engine/engine.css +16 -0
  18. data/app/helpers/maquina_components/breadcrumbs_helper.rb +118 -0
  19. data/app/helpers/maquina_components/dropdown_menu_helper.rb +249 -0
  20. data/app/helpers/maquina_components/empty_helper.rb +102 -0
  21. data/app/helpers/maquina_components/icons_helper.rb +161 -0
  22. data/app/helpers/maquina_components/pagination_helper.rb +153 -0
  23. data/app/helpers/maquina_components/sidebar_helper.rb +63 -0
  24. data/app/helpers/maquina_components/table_helper.rb +144 -0
  25. data/app/helpers/maquina_components/toggle_group_helper.rb +172 -0
  26. data/app/javascript/controllers/breadcrumb_controller.js +71 -0
  27. data/app/javascript/controllers/dropdown_menu_controller.js +203 -0
  28. data/app/javascript/controllers/menu_button_controller.js +59 -0
  29. data/app/javascript/controllers/sidebar_controller.js +316 -0
  30. data/app/javascript/controllers/sidebar_trigger_controller.js +32 -0
  31. data/app/javascript/controllers/toggle_group_controller.js +178 -0
  32. data/app/views/components/_alert.html.erb +12 -0
  33. data/app/views/components/_badge.html.erb +10 -0
  34. data/app/views/components/_breadcrumbs.html.erb +16 -0
  35. data/app/views/components/_card.html.erb +6 -0
  36. data/app/views/components/_dropdown.html.erb +25 -0
  37. data/app/views/components/_dropdown_menu.html.erb +9 -0
  38. data/app/views/components/_empty.html.erb +10 -0
  39. data/app/views/components/_header.html.erb +8 -0
  40. data/app/views/components/_menu_button.html.erb +44 -0
  41. data/app/views/components/_pagination.html.erb +13 -0
  42. data/app/views/components/_separator.html.erb +11 -0
  43. data/app/views/components/_sidebar.html.erb +40 -0
  44. data/app/views/components/_simple_table.html.erb +49 -0
  45. data/app/views/components/_table.html.erb +21 -0
  46. data/app/views/components/_toggle_group.html.erb +24 -0
  47. data/app/views/components/alert/_description.html.erb +6 -0
  48. data/app/views/components/alert/_title.html.erb +6 -0
  49. data/app/views/components/breadcrumbs/_ellipsis.html.erb +9 -0
  50. data/app/views/components/breadcrumbs/_item.html.erb +8 -0
  51. data/app/views/components/breadcrumbs/_link.html.erb +8 -0
  52. data/app/views/components/breadcrumbs/_list.html.erb +8 -0
  53. data/app/views/components/breadcrumbs/_page.html.erb +8 -0
  54. data/app/views/components/breadcrumbs/_separator.html.erb +17 -0
  55. data/app/views/components/card/_action.html.erb +6 -0
  56. data/app/views/components/card/_content.html.erb +9 -0
  57. data/app/views/components/card/_description.html.erb +6 -0
  58. data/app/views/components/card/_footer.html.erb +17 -0
  59. data/app/views/components/card/_header.html.erb +9 -0
  60. data/app/views/components/card/_title.html.erb +9 -0
  61. data/app/views/components/dropdown_menu/_content.html.erb +20 -0
  62. data/app/views/components/dropdown_menu/_group.html.erb +12 -0
  63. data/app/views/components/dropdown_menu/_item.html.erb +29 -0
  64. data/app/views/components/dropdown_menu/_label.html.erb +13 -0
  65. data/app/views/components/dropdown_menu/_separator.html.erb +11 -0
  66. data/app/views/components/dropdown_menu/_shortcut.html.erb +12 -0
  67. data/app/views/components/dropdown_menu/_trigger.html.erb +24 -0
  68. data/app/views/components/empty/_content.html.erb +8 -0
  69. data/app/views/components/empty/_description.html.erb +12 -0
  70. data/app/views/components/empty/_header.html.erb +8 -0
  71. data/app/views/components/empty/_media.html.erb +13 -0
  72. data/app/views/components/empty/_title.html.erb +12 -0
  73. data/app/views/components/pagination/_content.html.erb +8 -0
  74. data/app/views/components/pagination/_ellipsis.html.erb +28 -0
  75. data/app/views/components/pagination/_item.html.erb +8 -0
  76. data/app/views/components/pagination/_link.html.erb +23 -0
  77. data/app/views/components/pagination/_next.html.erb +57 -0
  78. data/app/views/components/pagination/_previous.html.erb +57 -0
  79. data/app/views/components/sidebar/_content.html.erb +8 -0
  80. data/app/views/components/sidebar/_footer.html.erb +8 -0
  81. data/app/views/components/sidebar/_group.html.erb +12 -0
  82. data/app/views/components/sidebar/_header.html.erb +8 -0
  83. data/app/views/components/sidebar/_inset.html.erb +8 -0
  84. data/app/views/components/sidebar/_menu.html.erb +8 -0
  85. data/app/views/components/sidebar/_menu_button.html.erb +14 -0
  86. data/app/views/components/sidebar/_menu_item.html.erb +7 -0
  87. data/app/views/components/sidebar/_menu_link.html.erb +32 -0
  88. data/app/views/components/sidebar/_provider.html.erb +16 -0
  89. data/app/views/components/sidebar/_trigger.html.erb +12 -0
  90. data/app/views/components/stats/_stats_card.html.erb +100 -0
  91. data/app/views/components/stats/_stats_grid.html.erb +38 -0
  92. data/app/views/components/table/_body.html.erb +5 -0
  93. data/app/views/components/table/_caption.html.erb +5 -0
  94. data/app/views/components/table/_cell.html.erb +5 -0
  95. data/app/views/components/table/_footer.html.erb +5 -0
  96. data/app/views/components/table/_head.html.erb +8 -0
  97. data/app/views/components/table/_header.html.erb +8 -0
  98. data/app/views/components/table/_row.html.erb +8 -0
  99. data/app/views/components/toggle_group/_item.html.erb +19 -0
  100. data/config/importmap.rb +1 -0
  101. data/lib/generators/maquina_components/install/USAGE +39 -0
  102. data/lib/generators/maquina_components/install/install_generator.rb +123 -0
  103. data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +68 -0
  104. data/lib/generators/maquina_components/install/templates/theme.css.tt +179 -0
  105. data/lib/maquina_components/engine.rb +10 -0
  106. data/lib/maquina_components/version.rb +1 -1
  107. metadata +121 -5
@@ -0,0 +1,161 @@
1
+ module MaquinaComponents
2
+ module IconsHelper
3
+ def icon_for(name, options = {})
4
+ return nil unless name
5
+
6
+ svg = icon_svg_for(name.to_sym) || main_icon_svg_for(name.to_sym)
7
+ return nil unless svg
8
+
9
+ css_classes = options[:class]
10
+ svg = svg.gsub('class="', "class=\"#{css_classes} ")
11
+
12
+ if options[:stroke_width]
13
+ svg = svg.gsub('stroke-width="2"', "stroke-width=\"#{options[:stroke_width]}\"")
14
+ end
15
+
16
+ svg.html_safe
17
+ end
18
+
19
+ def main_icon_svg_for(name)
20
+ end
21
+
22
+ def icon_svg_for(name)
23
+ case name
24
+ when :dollar
25
+ <<~SVG.freeze
26
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
27
+ <line x1="12" y1="2" x2="12" y2="22"></line>
28
+ <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>
29
+ </svg>
30
+ SVG
31
+ when :users
32
+ <<~SVG.freeze
33
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
34
+ <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path>
35
+ <circle cx="9" cy="7" r="4"></circle>
36
+ <path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"></path>
37
+ </svg>
38
+ SVG
39
+ when :credit_card
40
+ <<~SVG.freeze
41
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
42
+ <rect width="20" height="14" x="2" y="5" rx="2"></rect>
43
+ <line x1="2" y1="10" x2="22" y2="10"></line>
44
+ </svg>
45
+ SVG
46
+ when :activity
47
+ <<~SVG.freeze
48
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
49
+ <path d="M22 12h-4l-3 9L9 3l-3 9H2"></path>
50
+ </svg>
51
+ SVG
52
+ when :trend_up
53
+ <<~SVG.freeze
54
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
55
+ <polyline points="22 7 13.5 15.5 8.5 10.5 2 17"></polyline>
56
+ <polyline points="16 7 22 7 22 13"></polyline>
57
+ </svg>
58
+ SVG
59
+ when :trend_down
60
+ <<~SVG.freeze
61
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
62
+ <polyline points="22 17 13.5 8.5 8.5 13.5 2 7"></polyline>
63
+ <polyline points="16 17 22 17 22 11"></polyline>
64
+ </svg>
65
+ SVG
66
+ when :clock
67
+ <<~SVG.freeze
68
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
69
+ <circle cx="12" cy="12" r="10"></circle>
70
+ <polyline points="12 6 12 12 16 14"></polyline>
71
+ </svg>
72
+ SVG
73
+ when :money
74
+ <<~SVG.freeze
75
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
76
+ <rect width="20" height="12" x="2" y="6" rx="2"></rect>
77
+ <circle cx="12" cy="12" r="2"></circle>
78
+ <path d="M6 12h.01M18 12h.01"></path>
79
+ </svg>
80
+ SVG
81
+ when :line_chart
82
+ <<~SVG.freeze
83
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
84
+ <path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/>
85
+ </svg>
86
+ SVG
87
+ when :piggy_bank
88
+ <<~SVG.freeze
89
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
90
+ <path d="M19 5c-1.5 0-2.8 1.4-3 2-3.5-1.5-11-.3-11 5 0 1.8 0 3 2 4.5V20h4v-2h3v2h4v-4c1-.5 1.7-1 2-2h2v-4h-2c0-1-.5-1.5-1-2h0V5z"/>
91
+ <path d="M2 9v1c0 1.1.9 2 2 2h1"/>
92
+ <path d="M16 11h0"/>
93
+ </svg>
94
+ SVG
95
+ when :arrow_left
96
+ <<~SVG.freeze
97
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
98
+ <line x1="19" y1="12" x2="5" y2="12"></line>
99
+ <polyline points="12 19 5 12 12 5"></polyline>
100
+ </svg>
101
+ SVG
102
+ when :select_chevron
103
+ <<~SVG.freeze
104
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="" aria-hidden="true">
105
+ <path d="m6 9 6 6 6-6"></path>
106
+ </svg>
107
+ SVG
108
+ when :check
109
+ <<~SVG.freeze
110
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
111
+ <path d="M20 6 9 17l-5-5"/>
112
+ </svg>
113
+ SVG
114
+ when :circle_alert
115
+ <<~SVG.freeze
116
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
117
+ <circle cx="12" cy="12" r="10"/>
118
+ <line x1="12" x2="12" y1="8" y2="12"/>
119
+ <line x1="12" x2="12.01" y1="16" y2="16"/>
120
+ </svg>
121
+ SVG
122
+ when :logout
123
+ <<~SVG.freeze
124
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
125
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
126
+ <polyline points="16 17 21 12 16 7"></polyline>
127
+ <line x1="21" x2="9" y1="12" y2="12"></line>
128
+ </svg>
129
+ SVG
130
+ when :chevron_up_down
131
+ <<~SVG.freeze
132
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
133
+ <path d="m7 15 5 5 5-5"></path>
134
+ <path d="m7 9 5-5 5 5"></path>
135
+ </svg>
136
+ SVG
137
+ when :chevron_right
138
+ <<~SVG
139
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
140
+ <path d="m9 18 6-6-6-6"/>
141
+ </svg>
142
+ SVG
143
+ when :left_panel
144
+ <<~SVG.freeze
145
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="">
146
+ <rect width="18" height="18" x="3" y="3" rx="2"></rect>
147
+ <path d="M9 3v18"></path>
148
+ </svg>
149
+ SVG
150
+ when :ellipsis
151
+ <<~SVG.freeze
152
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
153
+ <circle cx="12" cy="12" r="1"/>
154
+ <circle cx="19" cy="12" r="1"/>
155
+ <circle cx="5" cy="12" r="1"/>
156
+ </svg>
157
+ SVG
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaquinaComponents
4
+ # Pagination Helper
5
+ #
6
+ # Provides convenient methods for creating pagination components with Pagy integration.
7
+ #
8
+ # @example Using the helper with Pagy
9
+ # <%%= pagination_nav(@pagy, :users_path) %>
10
+ #
11
+ # @example With additional params
12
+ # <%%= pagination_nav(@pagy, :search_users_path, params: { q: params[:q] }) %>
13
+ #
14
+ # @example With Turbo options
15
+ # <%%= pagination_nav(@pagy, :users_path, turbo: { action: :replace, frame: "users" }) %>
16
+ #
17
+ # @example Using partials directly
18
+ # <%%= render "components/pagination" do %>
19
+ # <%%= render "components/pagination/content" do %>
20
+ # <%%= render "components/pagination/item" do %>
21
+ # <%%= render "components/pagination/previous", href: prev_path %>
22
+ # <%% end %>
23
+ # ...
24
+ # <%% end %>
25
+ # <%% end %>
26
+ #
27
+ module PaginationHelper
28
+ # Renders a complete pagination navigation from a Pagy object
29
+ #
30
+ # @param pagy [Pagy] The Pagy pagination object
31
+ # @param route_helper [Symbol] Route helper method name (e.g., :users_path)
32
+ # @param params [Hash] Additional params to pass to route helper
33
+ # @param turbo [Hash] Turbo-specific data attributes
34
+ # @param show_labels [Boolean] Whether to show Previous/Next text labels
35
+ # @param css_classes [String] Additional CSS classes for the nav
36
+ # @return [String] Rendered HTML
37
+ def pagination_nav(pagy, route_helper, params: {}, turbo: {action: :replace}, show_labels: true, css_classes: "", **html_options)
38
+ return if pagy.pages <= 1
39
+
40
+ render "components/pagination", css_classes: css_classes, **html_options do
41
+ render "components/pagination/content" do
42
+ safe_join([
43
+ pagination_previous_item(pagy, route_helper, params, turbo, show_labels),
44
+ pagination_page_items(pagy, route_helper, params, turbo),
45
+ pagination_next_item(pagy, route_helper, params, turbo, show_labels)
46
+ ])
47
+ end
48
+ end
49
+ end
50
+
51
+ # Simpler pagination with just Previous/Next (no page numbers)
52
+ #
53
+ # @param pagy [Pagy] The Pagy pagination object
54
+ # @param route_helper [Symbol] Route helper method name
55
+ # @param params [Hash] Additional params to pass to route helper
56
+ # @param turbo [Hash] Turbo-specific data attributes
57
+ # @return [String] Rendered HTML
58
+ def pagination_simple(pagy, route_helper, params: {}, turbo: {action: :replace}, css_classes: "", **html_options)
59
+ return if pagy.pages <= 1
60
+
61
+ render "components/pagination", css_classes: css_classes, **html_options do
62
+ render "components/pagination/content" do
63
+ safe_join([
64
+ pagination_previous_item(pagy, route_helper, params, turbo, true),
65
+ pagination_next_item(pagy, route_helper, params, turbo, true)
66
+ ])
67
+ end
68
+ end
69
+ end
70
+
71
+ # Build paginated path with page param
72
+ #
73
+ # @param route_helper [Symbol] Route helper method name
74
+ # @param pagy [Pagy] The Pagy pagination object
75
+ # @param page [Integer] Page number
76
+ # @param extra_params [Hash] Additional params
77
+ # @return [String] URL path
78
+ def paginated_path(route_helper, pagy, page, extra_params = {})
79
+ page_param = pagy.vars[:page_param] || Pagy::DEFAULT[:page_param]
80
+ query_params = request.query_parameters.except(page_param.to_s).merge(extra_params)
81
+ query_params[page_param] = page
82
+
83
+ send(route_helper, query_params)
84
+ end
85
+
86
+ private
87
+
88
+ def pagination_previous_item(pagy, route_helper, params, turbo, show_label)
89
+ render "components/pagination/item" do
90
+ if pagy.prev
91
+ render "components/pagination/previous",
92
+ href: paginated_path(route_helper, pagy, pagy.prev, params),
93
+ show_label: show_label,
94
+ data: turbo_data(turbo)
95
+ else
96
+ render "components/pagination/previous",
97
+ disabled: true,
98
+ show_label: show_label
99
+ end
100
+ end
101
+ end
102
+
103
+ def pagination_next_item(pagy, route_helper, params, turbo, show_label)
104
+ render "components/pagination/item" do
105
+ if pagy.next
106
+ render "components/pagination/next",
107
+ href: paginated_path(route_helper, pagy, pagy.next, params),
108
+ show_label: show_label,
109
+ data: turbo_data(turbo)
110
+ else
111
+ render "components/pagination/next",
112
+ disabled: true,
113
+ show_label: show_label
114
+ end
115
+ end
116
+ end
117
+
118
+ def pagination_page_items(pagy, route_helper, params, turbo)
119
+ pagy.series.map do |item|
120
+ render "components/pagination/item" do
121
+ case item
122
+ when Integer
123
+ render "components/pagination/link",
124
+ href: paginated_path(route_helper, pagy, item, params),
125
+ active: item == pagy.page,
126
+ data: turbo_data(turbo) do
127
+ item.to_s
128
+ end
129
+ when String
130
+ # Current page (string representation)
131
+ render "components/pagination/link",
132
+ href: paginated_path(route_helper, pagy, item.to_i, params),
133
+ active: true,
134
+ data: turbo_data(turbo) do
135
+ item
136
+ end
137
+ when :gap
138
+ render "components/pagination/ellipsis"
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def turbo_data(turbo)
145
+ return {} if turbo.blank?
146
+
147
+ data = {}
148
+ data[:turbo_action] = turbo[:action] if turbo[:action]
149
+ data[:turbo_frame] = turbo[:frame] if turbo[:frame]
150
+ data
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,63 @@
1
+ module MaquinaComponents
2
+ module SidebarHelper
3
+ # Get sidebar state from cookie
4
+ #
5
+ # Reads the sidebar state cookie and returns a String.
6
+ # Use this to set the state value in the sidebar
7
+ # to ensure server-rendered state matches client state.
8
+ #
9
+ # @param cookie_name [String] The cookie name (default: "sidebar_state")
10
+ # @return [String] expanded if sidebar should be open, collapsed otherwise
11
+ #
12
+ # @example In layout
13
+ # <%= render "components/sidebar",
14
+ # state: sidebar_state do %>
15
+ # <!-- content -->
16
+ # <% end %>
17
+ #
18
+ # @example With custom cookie name
19
+ # <%= render "components/sidebar",
20
+ # state: sidebar_state("custom_sidebar_cookie") do %>
21
+ # <!-- content -->
22
+ # <% end %>
23
+ #
24
+ def sidebar_state(cookie_name = "sidebar_state")
25
+ # Read cookie value
26
+ cookie_value = cookies[cookie_name]
27
+
28
+ # Default to expanded when no cookie exists
29
+ return :expanded if cookie_value.nil?
30
+
31
+ # Return expanded if cookie says "true", otherwise collapsed
32
+ (cookie_value == "true") ? :expanded : :collapsed
33
+ end
34
+
35
+ # Check if sidebar is currently open
36
+ #
37
+ # @param cookie_name [String] The cookie name (default: "sidebar_state")
38
+ # @return [Boolean] true if sidebar is open
39
+ #
40
+ # @example
41
+ # <% if sidebar_open? %>
42
+ # <!-- Show sidebar-specific content -->
43
+ # <% end %>
44
+ #
45
+ def sidebar_open?(cookie_name = "sidebar_state")
46
+ sidebar_state(cookie_name) == :expanded
47
+ end
48
+
49
+ # Check if sidebar is currently closed
50
+ #
51
+ # @param cookie_name [String] The cookie name (default: "sidebar_state")
52
+ # @return [Boolean] true if sidebar is closed
53
+ #
54
+ # @example
55
+ # <% if sidebar_closed? %>
56
+ # <!-- Show expanded content when sidebar is closed -->
57
+ # <% end %>
58
+ #
59
+ def sidebar_closed?(cookie_name = "sidebar_state")
60
+ sidebar_state(cookie_name) == :collapsed
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaquinaComponents
4
+ # Table Component Helper
5
+ #
6
+ # Provides a simple helper for rendering basic tables from collections.
7
+ # For complex tables, use the partials directly for full control.
8
+ #
9
+ # @example Basic usage with collection
10
+ # <%= simple_table @invoices,
11
+ # columns: [
12
+ # { key: :number, label: "Invoice" },
13
+ # { key: :status, label: "Status" },
14
+ # { key: :amount, label: "Amount", align: :right }
15
+ # ] %>
16
+ #
17
+ # @example With caption and bordered variant
18
+ # <%= simple_table @users,
19
+ # columns: [
20
+ # { key: :name, label: "Name" },
21
+ # { key: :email, label: "Email" },
22
+ # { key: :role, label: "Role" }
23
+ # ],
24
+ # caption: "Active users",
25
+ # variant: :bordered %>
26
+ #
27
+ module TableHelper
28
+ # Render a simple table from a collection
29
+ #
30
+ # @param collection [Array, ActiveRecord::Relation] The collection to render
31
+ # @param columns [Array<Hash>] Column definitions with :key, :label, and optional :align
32
+ # @param caption [String, nil] Optional table caption
33
+ # @param variant [Symbol, nil] Container variant (:bordered)
34
+ # @param table_variant [Symbol, nil] Table variant (:striped)
35
+ # @param empty_message [String] Message to show when collection is empty
36
+ # @param row_id [Symbol, nil] Method to call for row ID (e.g., :id)
37
+ # @param html_options [Hash] Additional HTML options for the table
38
+ # @return [String] Rendered HTML
39
+ def simple_table(collection, columns:, caption: nil, variant: nil, table_variant: nil, empty_message: "No data available", row_id: nil, **html_options)
40
+ render partial: "components/simple_table", locals: {
41
+ collection: collection,
42
+ columns: columns,
43
+ caption: caption,
44
+ variant: variant,
45
+ table_variant: table_variant,
46
+ empty_message: empty_message,
47
+ row_id: row_id,
48
+ html_options: html_options
49
+ }
50
+ end
51
+
52
+ # Generate data attributes for table elements
53
+ # Useful when composing tables with other Rails helpers
54
+ #
55
+ # @example Using with content_tag
56
+ # <%= content_tag :table, **table_data_attrs do %>
57
+ # ...
58
+ # <% end %>
59
+ #
60
+ # @param variant [Symbol, nil] Table variant (:striped)
61
+ # @return [Hash] Data attributes hash
62
+ def table_data_attrs(variant: nil)
63
+ attrs = { data: { component: "table" } }
64
+ attrs[:data][:variant] = variant.to_s if variant
65
+ attrs
66
+ end
67
+
68
+ # Generate data attributes for table container
69
+ #
70
+ # @param variant [Symbol, nil] Container variant (:bordered)
71
+ # @return [Hash] Data attributes hash
72
+ def table_container_data_attrs(variant: nil)
73
+ attrs = { data: { table_part: "container" } }
74
+ attrs[:data][:variant] = variant.to_s if variant
75
+ attrs
76
+ end
77
+
78
+ # Generate data attributes for table row
79
+ #
80
+ # @param selected [Boolean] Whether the row is selected
81
+ # @return [Hash] Data attributes hash
82
+ def table_row_data_attrs(selected: false)
83
+ attrs = { data: { table_part: "row" } }
84
+ attrs[:data][:state] = "selected" if selected
85
+ attrs
86
+ end
87
+
88
+ # Generate data attributes for table header
89
+ #
90
+ # @param sticky [Boolean] Whether the header is sticky
91
+ # @return [Hash] Data attributes hash
92
+ def table_header_data_attrs(sticky: false)
93
+ attrs = { data: { table_part: "header" } }
94
+ attrs[:data][:sticky] = "true" if sticky
95
+ attrs
96
+ end
97
+
98
+ # Generate data attributes for table head cell
99
+ # @return [Hash] Data attributes hash
100
+ def table_head_data_attrs
101
+ { data: { table_part: "head" } }
102
+ end
103
+
104
+ # Generate data attributes for table cell
105
+ #
106
+ # @param empty [Boolean] Whether this is an empty state cell
107
+ # @return [Hash] Data attributes hash
108
+ def table_cell_data_attrs(empty: false)
109
+ attrs = { data: { table_part: "cell" } }
110
+ attrs[:data][:empty] = "true" if empty
111
+ attrs
112
+ end
113
+
114
+ # Generate data attributes for table body
115
+ # @return [Hash] Data attributes hash
116
+ def table_body_data_attrs
117
+ { data: { table_part: "body" } }
118
+ end
119
+
120
+ # Generate data attributes for table footer
121
+ # @return [Hash] Data attributes hash
122
+ def table_footer_data_attrs
123
+ { data: { table_part: "footer" } }
124
+ end
125
+
126
+ # Generate data attributes for table caption
127
+ # @return [Hash] Data attributes hash
128
+ def table_caption_data_attrs
129
+ { data: { table_part: "caption" } }
130
+ end
131
+
132
+ # Convert alignment symbol to CSS class
133
+ #
134
+ # @param align [Symbol, nil] Alignment (:left, :center, :right)
135
+ # @return [String, nil] CSS class name
136
+ def table_alignment_class(align)
137
+ case align&.to_sym
138
+ when :right then "text-right"
139
+ when :center then "text-center"
140
+ else nil
141
+ end
142
+ end
143
+ end
144
+ end