plutonium 0.14.0 → 0.15.0.pre.rc1

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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/README copy.md +1 -1
  3. data/README.md +1 -1
  4. data/app/assets/plutonium.css +1 -1
  5. data/app/views/{application → plutonium}/_resource_header.html copy.erb +1 -1
  6. data/app/views/{application → plutonium}/_resource_header.html.erb +1 -1
  7. data/app/views/{application → plutonium}/_resource_sidebar.html.erb +2 -0
  8. data/app/views/resource/_resource_details.html.erb +1 -36
  9. data/app/views/resource/_resource_form.html.erb +1 -5
  10. data/app/views/resource/_resource_table.html.erb +315 -85
  11. data/app/views/resource/edit.html.erb +1 -5
  12. data/app/views/resource/index.html.erb +1 -5
  13. data/app/views/resource/new.html.erb +1 -5
  14. data/app/views/resource/show.html.erb +1 -5
  15. data/config/initializers/pagy.rb +1 -0
  16. data/config/initializers/rabl.rb +27 -20
  17. data/gemfiles/rails_7.gemfile.lock +5 -1
  18. data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
  19. data/lib/generators/pu/core/install/install_generator.rb +0 -3
  20. data/lib/generators/pu/core/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
  21. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
  22. data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
  23. data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
  24. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +5 -2
  25. data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
  26. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
  27. data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
  28. data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
  29. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
  30. data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
  31. data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
  32. data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
  33. data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
  34. data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
  35. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
  36. data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
  37. data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
  38. data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
  39. data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
  40. data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
  41. data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
  42. data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
  43. data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
  44. data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
  45. data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
  46. data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
  47. data/lib/generators/pu/res/model/model_generator.rb +0 -7
  48. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
  49. data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
  50. data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
  51. data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
  52. data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
  53. data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
  54. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
  55. data/lib/plutonium/action/README.md +0 -0
  56. data/lib/plutonium/action/base.rb +103 -0
  57. data/lib/plutonium/action/interactive.rb +117 -0
  58. data/lib/plutonium/action/route_options.rb +65 -0
  59. data/lib/plutonium/action/simple.rb +8 -0
  60. data/lib/plutonium/auth.rb +1 -1
  61. data/lib/plutonium/configuration.rb +130 -0
  62. data/lib/plutonium/core/actions/collection.rb +1 -1
  63. data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
  64. data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
  65. data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
  66. data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
  67. data/lib/plutonium/core/controller.rb +110 -0
  68. data/lib/plutonium/core/controllers/authorizable.rb +12 -35
  69. data/lib/plutonium/core/controllers/bootable.rb +38 -7
  70. data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
  71. data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
  72. data/lib/plutonium/core/ui/collection.rb +1 -1
  73. data/lib/plutonium/core/ui/detail.rb +1 -1
  74. data/lib/plutonium/core/ui/form.rb +1 -1
  75. data/lib/plutonium/definition/actions.rb +50 -0
  76. data/lib/plutonium/definition/base.rb +92 -0
  77. data/lib/plutonium/definition/config_attr.rb +30 -0
  78. data/lib/plutonium/definition/defineable_props.rb +96 -0
  79. data/lib/plutonium/definition/search.rb +21 -0
  80. data/lib/plutonium/engine/validator.rb +30 -0
  81. data/lib/plutonium/engine.rb +25 -0
  82. data/lib/plutonium/helpers/assets_helper.rb +73 -20
  83. data/lib/plutonium/helpers/form_helper.rb +1 -3
  84. data/lib/plutonium/interaction/README.md +369 -0
  85. data/lib/plutonium/interaction/base.rb +75 -0
  86. data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
  87. data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
  88. data/lib/plutonium/interaction/outcome.rb +129 -0
  89. data/lib/plutonium/interaction/response/base.rb +63 -0
  90. data/lib/plutonium/interaction/response/null.rb +33 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +30 -0
  92. data/lib/plutonium/interaction/response/render.rb +28 -0
  93. data/lib/plutonium/lib/bit_flags.rb +70 -9
  94. data/lib/plutonium/lib/overlayed_hash.rb +86 -0
  95. data/lib/plutonium/lib/smart_cache.rb +171 -0
  96. data/lib/plutonium/models/has_cents.rb +170 -0
  97. data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
  98. data/lib/plutonium/{application → portal}/controller.rb +3 -11
  99. data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
  100. data/lib/plutonium/portal/engine.rb +15 -0
  101. data/lib/plutonium/railtie.rb +35 -15
  102. data/lib/plutonium/reloader.rb +71 -29
  103. data/lib/plutonium/resource/controller.rb +51 -34
  104. data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
  105. data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
  106. data/lib/plutonium/resource/controllers/defineable.rb +26 -0
  107. data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
  108. data/lib/plutonium/resource/controllers/presentable.rb +41 -0
  109. data/lib/plutonium/resource/controllers/queryable.rb +44 -0
  110. data/lib/plutonium/resource/definition.rb +6 -0
  111. data/lib/plutonium/resource/policy.rb +25 -13
  112. data/lib/plutonium/resource/query_object.rb +50 -51
  113. data/lib/plutonium/resource/record.rb +6 -89
  114. data/lib/plutonium/resource/register.rb +82 -0
  115. data/lib/plutonium/routing/mapper_extensions.rb +1 -1
  116. data/lib/plutonium/routing/resource_registration.rb +1 -1
  117. data/lib/plutonium/routing/route_set_extensions.rb +6 -18
  118. data/lib/plutonium/ui/action_button.rb +125 -0
  119. data/lib/plutonium/ui/breadcrumbs.rb +163 -0
  120. data/lib/plutonium/ui/component/base.rb +13 -0
  121. data/lib/plutonium/ui/component/behaviour.rb +38 -0
  122. data/lib/plutonium/ui/component/kit.rb +31 -0
  123. data/lib/plutonium/ui/component/methods.rb +54 -0
  124. data/lib/plutonium/ui/display/base.rb +25 -0
  125. data/lib/plutonium/ui/display/component/association.rb +26 -0
  126. data/lib/plutonium/ui/display/resource.rb +77 -0
  127. data/lib/plutonium/ui/display/theme.rb +27 -0
  128. data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
  129. data/lib/plutonium/ui/empty_card.rb +20 -0
  130. data/lib/plutonium/ui/form/base.rb +37 -0
  131. data/lib/plutonium/ui/form/resource.rb +75 -0
  132. data/lib/plutonium/ui/form/theme.rb +42 -0
  133. data/lib/plutonium/ui/page/base.rb +112 -0
  134. data/lib/plutonium/ui/page/edit.rb +23 -0
  135. data/lib/plutonium/ui/page/index.rb +27 -0
  136. data/lib/plutonium/ui/page/new.rb +23 -0
  137. data/lib/plutonium/ui/page/show.rb +27 -0
  138. data/lib/plutonium/ui/page_header.rb +49 -0
  139. data/lib/plutonium/ui/table/base.rb +13 -0
  140. data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
  141. data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
  142. data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
  143. data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
  144. data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
  145. data/lib/plutonium/ui/table/display_theme.rb +21 -0
  146. data/lib/plutonium/ui/table/resource.rb +98 -0
  147. data/lib/plutonium/ui/table/theme.rb +35 -0
  148. data/lib/plutonium/ui.rb +9 -0
  149. data/lib/plutonium/version.rb +5 -1
  150. data/lib/plutonium.rb +53 -26
  151. data/package-lock.json +19 -22
  152. data/package.json +4 -4
  153. data/sig/.keep +0 -0
  154. data/src/css/plutonium.css +15 -0
  155. data/tailwind.options.js +11 -3
  156. metadata +220 -81
  157. data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
  158. data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
  159. data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
  160. data/lib/plutonium/concerns/resource_validatable.rb +0 -34
  161. data/lib/plutonium/config.rb +0 -9
  162. data/lib/plutonium/core/controllers/base.rb +0 -101
  163. data/lib/plutonium/core/controllers/presentable.rb +0 -65
  164. data/lib/plutonium/core/controllers/queryable.rb +0 -28
  165. data/lib/plutonium/pkg/app.rb +0 -35
  166. data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
  167. data/lib/plutonium/pkg/feature.rb +0 -18
  168. data/lib/plutonium/policy/initializer.rb +0 -22
  169. data/lib/plutonium/policy/scope.rb +0 -19
  170. data/lib/plutonium/pundit/context.rb +0 -18
  171. data/lib/plutonium/pundit/policy_finder.rb +0 -25
  172. data/lib/plutonium/resource/policy_context.rb +0 -5
  173. data/lib/plutonium/resource_register.rb +0 -83
  174. data/lib/plutonium/smart_cache.rb +0 -151
  175. data/sig/plutonium.rbs +0 -12
  176. /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
  177. /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
  178. /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
  179. /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
  180. /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
  181. /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
  182. /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
  183. /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  184. /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
  185. /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
  186. /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
  187. /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
  188. /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
  189. /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
  190. /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Table
6
+ module Components
7
+ class ScopesBar < Plutonium::UI::Component::Base
8
+ include Plutonium::UI::Component::Behaviour
9
+
10
+ def view_template
11
+ div(
12
+ class:
13
+ # "flex flex-wrap justify-between items-center gap-4 p-4 bg-white border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 mb-4"
14
+ "flex flex-wrap justify-between items-center gap-4 mb-4"
15
+ ) do
16
+ div(class: "flex flex-wrap items-center gap-2") do
17
+ name = "all"
18
+ if current_scope.blank?
19
+ a(
20
+ id: "#{name}-scope",
21
+ href: current_query_object.build_url(scope: nil),
22
+ class:
23
+ "px-4 py-2 text-sm font-medium text-white bg-primary-700 border border-primary-700 rounded-lg hover:bg-primary-800 focus:z-10 focus:ring-2 focus:ring-primary-700 focus:text-white dark:bg-primary-600 dark:border-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
24
+ ) { name.humanize }
25
+ else
26
+ a(
27
+ id: "#{name}-scope",
28
+ href: current_query_object.build_url(scope: nil),
29
+ class:
30
+ "px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 focus:z-10 focus:ring-2 focus:ring-gray-300 focus:text-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-gray-700 dark:focus:text-white"
31
+ ) { name.humanize }
32
+ end
33
+
34
+ current_query_object.scope_definitions.each do |name, definition|
35
+ if name == current_scope
36
+ a(
37
+ id: "#{name}-scope",
38
+ href: current_query_object.build_url(scope: name),
39
+ class:
40
+ "px-4 py-2 text-sm font-medium text-white bg-primary-700 border border-primary-700 rounded-lg hover:bg-primary-800 focus:z-10 focus:ring-2 focus:ring-primary-700 focus:text-white dark:bg-primary-600 dark:border-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
41
+ ) { name.humanize }
42
+ else
43
+ a(
44
+ id: "#{name}-scope",
45
+ href: current_query_object.build_url(scope: name),
46
+ class:
47
+ "px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-100 hover:text-gray-700 focus:z-10 focus:ring-2 focus:ring-gray-300 focus:text-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-gray-700 dark:focus:text-white"
48
+ ) { name.humanize }
49
+ end
50
+ end
51
+ end
52
+
53
+ # div(class: "flex flex-wrap items-center gap-2") do
54
+ # button(
55
+ # class:
56
+ # "inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
57
+ # ) do
58
+ # svg(
59
+ # class: "w-4 h-4 mr-2",
60
+ # fill: "none",
61
+ # stroke: "currentColor",
62
+ # viewbox: "0 0 24 24",
63
+ # xmlns: "http://www.w3.org/2000/svg"
64
+ # ) do |s|
65
+ # s.path(
66
+ # stroke_linecap: "round",
67
+ # stroke_linejoin: "round",
68
+ # stroke_width: "2",
69
+ # d:
70
+ # "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
71
+ # )
72
+ # end
73
+ # plain " Delete Selected "
74
+ # end
75
+ # button(
76
+ # class:
77
+ # "inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-yellow-700 rounded-lg hover:bg-yellow-800 focus:ring-4 focus:outline-none focus:ring-yellow-300 dark:bg-yellow-600 dark:hover:bg-yellow-700 dark:focus:ring-yellow-800"
78
+ # ) do
79
+ # svg(
80
+ # class: "w-4 h-4 mr-2",
81
+ # fill: "none",
82
+ # stroke: "currentColor",
83
+ # viewbox: "0 0 24 24",
84
+ # xmlns: "http://www.w3.org/2000/svg"
85
+ # ) do |s|
86
+ # s.path(
87
+ # stroke_linecap: "round",
88
+ # stroke_linejoin: "round",
89
+ # stroke_width: "2",
90
+ # d:
91
+ # "M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
92
+ # )
93
+ # end
94
+ # plain " Archive Selected "
95
+ # end
96
+ # button(
97
+ # id: "dropdownActionButton",
98
+ # data_dropdown_toggle: "dropdownAction",
99
+ # class:
100
+ # "inline-flex items-center text-gray-500 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-3 py-2 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700",
101
+ # type: "button"
102
+ # ) do
103
+ # span(class: "sr-only") { "Action button" }
104
+ # plain " More Actions "
105
+ # svg(
106
+ # class: "w-2.5 h-2.5 ml-2.5",
107
+ # aria_hidden: "true",
108
+ # xmlns: "http://www.w3.org/2000/svg",
109
+ # fill: "none",
110
+ # viewbox: "0 0 10 6"
111
+ # ) do |s|
112
+ # s.path(
113
+ # stroke: "currentColor",
114
+ # stroke_linecap: "round",
115
+ # stroke_linejoin: "round",
116
+ # stroke_width: "2",
117
+ # d: "m1 1 4 4 4-4"
118
+ # )
119
+ # end
120
+ # end
121
+ # end
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def current_scope = resource_query_params[:scope]
128
+
129
+ def render?
130
+ current_query_object.scope_definitions.present?
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Table
6
+ module Components
7
+ class SearchBar < Plutonium::UI::Component::Base
8
+ def view_template
9
+ original_attributes = Phlex::HTML::EVENT_ATTRIBUTES
10
+ temp_attributes = Phlex::HTML::EVENT_ATTRIBUTES.dup
11
+ temp_attributes.delete("oninput")
12
+ temp_attributes.delete("onclick")
13
+ Phlex::HTML.const_set(:EVENT_ATTRIBUTES, temp_attributes)
14
+
15
+ div(
16
+ class:
17
+ # "p-4 bg-white border border-gray-200 rounded-lg dark:bg-gray-800 dark:border-gray-700 space-y-2 mb-4"
18
+ "space-y-2 mb-4"
19
+ ) do
20
+ search_query = current_query_object.search_query
21
+ query_params = resource_query_params
22
+ render Phlexi::Form(:q, attributes: {data: {controller: "form", turbo_frame: nil}}) {
23
+ div(class: "relative") do
24
+ div(class: "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none") do
25
+ svg(
26
+ class: "w-5 h-5 text-gray-500 dark:text-gray-400",
27
+ aria_hidden: "true",
28
+ fill: "currentColor",
29
+ viewbox: "0 0 20 20",
30
+ xmlns: "http://www.w3.org/2000/svg"
31
+ ) do |s|
32
+ s.path(
33
+ fill_rule: "evenodd",
34
+ d:
35
+ "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
36
+ clip_rule: "evenodd"
37
+ )
38
+ end
39
+ end
40
+ render field(:search, value: search_query)
41
+ .placeholder("Search...")
42
+ .input_tag(
43
+ id: "search",
44
+ value: search_query,
45
+ class: "block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500",
46
+ data: {
47
+ action: "form#submit",
48
+ turbo_permanent: true
49
+ }
50
+ )
51
+
52
+ render field(:scope, value: query_params[:scope]).input_tag(type: :hidden)
53
+ render field(:sort_fields, value: query_params[:sort_fields]).input_array_tag do |f|
54
+ render f.input_tag(type: :hidden)
55
+ end
56
+ nest_one(:sort_directions) do |directions|
57
+ query_params[:sort_directions]&.each do |name, value|
58
+ render directions.field(name, value:).input_tag(hidden: true)
59
+ end
60
+ end
61
+ end
62
+ }
63
+
64
+ # div(class: "flex flex-wrap items-center gap-4") do
65
+ # span(class: "text-sm font-medium text-gray-900 dark:text-white") do
66
+ # "Filters:"
67
+ # end
68
+ # select(
69
+ # id: "category-filter",
70
+ # class:
71
+ # "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
72
+ # ) do
73
+ # option(selected: "selected", value: "") { "All Categories" }
74
+ # option(value: "technology") { "Technology" }
75
+ # option(value: "science") { "Science" }
76
+ # option(value: "health") { "Health" }
77
+ # end
78
+ # div(class: "flex items-center space-x-2") do
79
+ # input(
80
+ # type: "date",
81
+ # id: "start-date",
82
+ # class:
83
+ # "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
84
+ # )
85
+ # span(class: "text-gray-500 dark:text-gray-400") { "to" }
86
+ # input(
87
+ # type: "date",
88
+ # id: "end-date",
89
+ # class:
90
+ # "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
91
+ # )
92
+ # end
93
+ # select(
94
+ # id: "author-filter",
95
+ # class:
96
+ # "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
97
+ # ) do
98
+ # option(selected: "selected", value: "") { "All Authors" }
99
+ # option(value: "john-doe") { "John Doe" }
100
+ # option(value: "jane-smith") { "Jane Smith" }
101
+ # end
102
+ # button(
103
+ # onclick: "applyFilters()",
104
+ # class:
105
+ # "inline-flex items-center text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"
106
+ # ) do
107
+ # svg(
108
+ # class: "w-4 h-4 mr-2",
109
+ # fill: "currentColor",
110
+ # viewbox: "0 0 20 20",
111
+ # xmlns: "http://www.w3.org/2000/svg"
112
+ # ) do |s|
113
+ # s.path(
114
+ # fill_rule: "evenodd",
115
+ # d:
116
+ # "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z",
117
+ # clip_rule: "evenodd"
118
+ # )
119
+ # end
120
+ # plain " Apply Filters "
121
+ # end
122
+ # button(
123
+ # onclick: "clearFilters()",
124
+ # class:
125
+ # "inline-flex items-center text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700"
126
+ # ) do
127
+ # svg(
128
+ # class: "w-4 h-4 mr-2",
129
+ # fill: "currentColor",
130
+ # viewbox: "0 0 20 20",
131
+ # xmlns: "http://www.w3.org/2000/svg"
132
+ # ) do |s|
133
+ # s.path(
134
+ # fill_rule: "evenodd",
135
+ # d:
136
+ # "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z",
137
+ # clip_rule: "evenodd"
138
+ # )
139
+ # end
140
+ # plain " Clear Filters "
141
+ # end
142
+ # end
143
+ end
144
+ ensure
145
+ # TODO: remove this once Phlex adds support for SafeValues
146
+ Phlex::HTML.const_set(:EVENT_ATTRIBUTES, original_attributes)
147
+ end
148
+
149
+ private
150
+
151
+ def render?
152
+ current_query_object.search_filter.present? && current_policy.allowed_to?(:search?)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Table
6
+ class DisplayTheme < Phlexi::Table::DisplayTheme
7
+ def self.theme
8
+ super.merge({
9
+ string: "max-h-[150px] overflow-y-auto",
10
+ prefixed_icon: "w-4 h-4 mr-1",
11
+ link: "text-primary-600 dark:text-primary-500",
12
+ color: "flex items-center",
13
+ color_indicator: "w-10 h-10 rounded-full mr-2",
14
+ email: "flex items-center text-primary-600 dark:text-primary-500 whitespace-nowrap",
15
+ json: "max-h-[150px] overflow-y-auto whitespace-pre font-mono shadow-inner p-4"
16
+ })
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Table
6
+ class Resource < Plutonium::UI::Component::Base
7
+ attr_reader :collection, :resource_fields, :resource_definition
8
+
9
+ def initialize(collection, resource_fields:, resource_definition:)
10
+ @collection = collection
11
+ @resource_fields = resource_fields
12
+ @resource_definition = resource_definition
13
+ end
14
+
15
+ def view_template
16
+ render_search_bar
17
+ render_scopes_bar
18
+
19
+ collection.empty? ? render_empty_card : render_table
20
+
21
+ render_footer
22
+ end
23
+
24
+ private
25
+
26
+ def render_search_bar
27
+ TableSearchBar()
28
+ end
29
+
30
+ def render_scopes_bar
31
+ TableScopesBar()
32
+ end
33
+
34
+ def render_empty_card
35
+ EmptyCard("No #{resource_name_plural(resource_class)} match your query") {
36
+ action = resource_definition.defined_actions[:new]
37
+ if action&.permitted_by?(current_policy)
38
+ url = resource_url_for(resource_class, *action.route_options.url_args, **action.route_options.url_options)
39
+ ActionButton(action, url:)
40
+ end
41
+ }
42
+ end
43
+
44
+ def render_table
45
+ render Plutonium::UI::Table::Base.new(collection) do |table|
46
+ @resource_fields.each do |name|
47
+ # column :name, as: :string
48
+ # column :description, class: "text-red-700"
49
+ # column :age, align: :end
50
+ # column :dob do |proxy|
51
+ # proxy.field(:dob).date_tag
52
+ # end
53
+
54
+ column_definition = resource_definition.defined_columns[name] || {}
55
+ column_display_options = column_definition[:options] || {}
56
+ display_field_as = column_display_options.delete(:as)
57
+ align_field_to = column_display_options.delete(:align)
58
+
59
+ display_block = column_definition[:block] || ->(wrapped_object, key) {
60
+ f = wrapped_object.field(key)
61
+ display_field_as ||= f.inferred_field_component
62
+ f.send(:"#{display_field_as}_tag", **column_display_options)
63
+ }
64
+
65
+ field_options = resource_definition.defined_fields[name] ? resource_definition.defined_fields[name][:options] : {}
66
+ field_options[:as] = display_field_as
67
+ field_options[:align] = align_field_to if align_field_to
68
+ table.column name, **field_options, sort_params: current_query_object.sort_params_for(name), &display_block
69
+ end
70
+
71
+ table.actions do |wrapped_object|
72
+ record = wrapped_object.unwrapped
73
+ policy = policy_for(record:)
74
+
75
+ div(class: "flex space-x-2") {
76
+ resource_definition.defined_actions
77
+ .select { |k, a| a.collection_record_action? && policy.allowed_to?(:"#{k}?") }
78
+ .values
79
+ .each { |action|
80
+ url = resource_url_for(record, *action.route_options.url_args, **action.route_options.url_options)
81
+
82
+ ActionButton(action, url:, variant: :table)
83
+ }
84
+ }
85
+ end
86
+ end
87
+ end
88
+
89
+ def render_footer
90
+ div(class: "sticky bottom-[-2px] p-4 pb-6 w-full z-50 bg-gray-50 dark:bg-gray-900") {
91
+ TableInfo(pagy_instance)
92
+ TablePagination(pagy_instance)
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Table
6
+ class Theme < Phlexi::Table::Theme
7
+ def self.theme
8
+ super.merge({
9
+ selection_checkbox: "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600",
10
+ name_column: "font-medium text-gray-900 whitespace-nowrap dark:text-white",
11
+ align_start: "text-start",
12
+ align_end: "text-end",
13
+ wrapper: "relative overflow-x-auto shadow-md sm:rounded-lg",
14
+ base: "w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400",
15
+ caption: "p-5 text-lg font-semibold text-left rtl:text-right text-gray-900 bg-white dark:text-white dark:bg-gray-800",
16
+ description: "mt-1 text-sm font-normal text-gray-500 dark:text-gray-400",
17
+ header: "text-xs text-gray-700 uppercase bg-gray-200 dark:bg-gray-700 dark:text-gray-400",
18
+ header_grouping_cell: "px-6 py-3 text-center text-sm border-b border-t border-r last:border-r-0 dark:border-gray-800",
19
+ header_cell: "px-6 py-3",
20
+ header_cell_content_wrapper: "inline-flex items-center",
21
+ header_cell_sort_wrapper: "flex items-center",
22
+ header_cell_sort_indicator: "ml-1.5",
23
+ body_row: "bg-white border-b last:border-none dark:bg-gray-800 dark:border-gray-700",
24
+ body_cell: "px-6 py-4 whitespace-pre max-w-[310px] hover:max-w-[600px] overflow-hidden text-ellipsis transition-all duration-300 ease-in-out",
25
+ sort_icon: "w-3 h-3",
26
+ sort_icon_active: "text-primary-600",
27
+ sort_index_clear_link: "ml-2",
28
+ sort_index_clear_link_text: "text-xs font-bold",
29
+ sort_index_clear_link_icon: "ml-1 text-red-600"
30
+ })
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tailwind_merge"
4
+
5
+ module Plutonium
6
+ module UI
7
+ TAILWIND_MERGER = TailwindMerge::Merger.new
8
+ end
9
+ end
@@ -1,3 +1,7 @@
1
1
  module Plutonium
2
- VERSION = "0.14.0"
2
+ VERSION = "0.15.0-rc1"
3
+ NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
+ v[1] = v[1].to_i + 1
5
+ v[2] = 0
6
+ }.join(".")
3
7
  end
data/lib/plutonium.rb CHANGED
@@ -1,57 +1,84 @@
1
- require "zeitwerk"
1
+ # frozen_string_literal: true
2
2
 
3
- # Zeitwerk loader setup for the Plutonium gem
4
- loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
5
- loader.ignore("#{__dir__}/generators")
6
- loader.ignore("#{__dir__}/plutonium/railtie.rb")
7
- loader.enable_reloading if defined?(Rails.env) && Rails.env.development?
8
- loader.setup
3
+ require "zeitwerk"
4
+ require "action_policy"
5
+ require "phlex"
6
+ require "phlex-rails"
7
+ require "phlex/tabler_icons"
8
+ require "phlexi-display"
9
+ require "phlexi-form"
10
+ require "phlexi-table"
9
11
 
10
- require_relative "plutonium/railtie" if defined?(Rails::Railtie)
12
+ require_relative "plutonium/configuration"
11
13
 
14
+ # Plutonium module
15
+ #
16
+ # This module provides the main functionality for the Plutonium gem.
17
+ # It sets up autoloading using Zeitwerk and provides utility methods
18
+ # for accessing gem-related information.
12
19
  module Plutonium
13
- # Custom error class for the Plutonium module
20
+ # Custom error class for Plutonium-specific exceptions
14
21
  class Error < StandardError; end
15
22
 
23
+ # Set up Zeitwerk loader for the Plutonium gem
24
+ # @return [Zeitwerk::Loader] configured Zeitwerk loader instance
25
+ Loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false).tap do |loader|
26
+ loader.ignore("#{__dir__}/generators")
27
+ loader.ignore("#{__dir__}/plutonium/railtie.rb")
28
+ loader.inflector.inflect("ui" => "UI")
29
+ loader.enable_reloading if defined?(Rails.env) && Rails.env.development?
30
+ loader.setup
31
+ end
32
+
16
33
  class << self
17
- # @return [Pathname] the root directory of the gem
34
+ def deprecator
35
+ @deprecator ||= ActiveSupport::Deprecation.new("1.0", "Plutonium")
36
+ end
37
+
38
+ # Get the root directory of the gem
39
+ #
40
+ # @return [Pathname] the root directory path
18
41
  def root
19
- Pathname.new(File.expand_path("..", __dir__))
42
+ @root ||= Pathname.new(File.expand_path("..", __dir__))
20
43
  end
21
44
 
22
- # @return [Pathname] the root directory of the lib folder of the gem
45
+ # Get the root directory of the lib folder of the gem
46
+ #
47
+ # @return [Pathname] the lib root directory path
23
48
  def lib_root
24
- root.join("lib", "plutonium")
49
+ @lib_root ||= root.join("lib", "plutonium")
25
50
  end
26
51
 
27
- # @return [Logger] the Rails logger
52
+ # Get the Rails logger
53
+ #
54
+ # @return [Logger] the Rails logger instance
28
55
  def logger
29
56
  Rails.logger
30
57
  end
31
58
 
32
- # @return [String] the name of the application
59
+ # Get the name of the application
60
+ #
61
+ # @return [String] the application name
33
62
  def application_name
34
63
  @application_name || Rails.application.class.module_parent_name
35
64
  end
36
65
 
37
- # @param [String] application_name the name of the application
38
- # @return [void]
66
+ # Set the name of the application
67
+ #
68
+ # @param [String] name the name of the application
39
69
  attr_writer :application_name
40
70
 
41
- # @return [Boolean] whether the gem is in development mode
42
- def development?
43
- ActiveModel::Type::Boolean.new.cast(ENV["PLUTONIUM_DEV"]).present?
44
- end
45
-
46
- # Eager loads Rails application if not already eager loaded
71
+ # Eager load Rails application if not already eager loaded
72
+ #
47
73
  # @return [void]
48
74
  def eager_load_rails!
49
- return if Rails.env.production? && defined?(@rails_eager_loaded)
75
+ return if @rails_eager_loaded || Rails.application.config.eager_load
50
76
 
51
- Rails.application.eager_load! unless Rails.application.config.eager_load
77
+ Rails.application.eager_load!
52
78
  @rails_eager_loaded = true
53
79
  end
54
80
  end
55
81
  end
56
82
 
57
- Plutonium::ZEITWERK_LOADER = loader
83
+ # Load Railtie if Rails is defined
84
+ require_relative "plutonium/railtie" if defined?(Rails::Railtie)