plutonium 0.15.5 → 0.15.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +1 -1
  3. data/app/assets/plutonium.ico +0 -0
  4. data/app/assets/plutonium.js +25 -11
  5. data/app/assets/plutonium.js.map +2 -2
  6. data/app/assets/plutonium.min.js +4 -4
  7. data/app/assets/plutonium.min.js.map +3 -3
  8. data/app/assets/plutonium.png +0 -0
  9. data/app/views/layouts/rodauth.html.erb +2 -2
  10. data/docs/.vitepress/config.ts +61 -0
  11. data/docs/.vitepress/theme/custom.css +61 -0
  12. data/docs/.vitepress/theme/index.ts +4 -0
  13. data/docs/api-examples.md +49 -0
  14. data/docs/guide/getting-started/authorization.md +296 -0
  15. data/docs/guide/getting-started/core-concepts.md +432 -0
  16. data/docs/guide/getting-started/index.md +18 -0
  17. data/docs/guide/getting-started/installation.md +270 -0
  18. data/docs/guide/getting-started/resources.md +250 -0
  19. data/docs/guide/what-is-plutonium.md +211 -0
  20. data/docs/index.md +43 -0
  21. data/docs/markdown-examples.md +85 -0
  22. data/docs/public/android-chrome-192x192.png +0 -0
  23. data/docs/public/android-chrome-512x512.png +0 -0
  24. data/docs/public/apple-touch-icon.png +0 -0
  25. data/docs/public/favicon-16x16.png +0 -0
  26. data/docs/public/favicon-32x32.png +0 -0
  27. data/docs/public/favicon.ico +0 -0
  28. data/docs/public/plutonium.png +0 -0
  29. data/docs/public/site.webmanifest +1 -0
  30. data/docs/public/templates/plutonium.rb +29 -0
  31. data/lib/generators/pu/core/assets/assets_generator.rb +2 -3
  32. data/lib/generators/pu/core/assets/templates/tailwind.config.js +2 -2
  33. data/lib/generators/pu/core/install/install_generator.rb +9 -1
  34. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +0 -1
  35. data/lib/generators/pu/eject/layout/layout_generator.rb +3 -3
  36. data/lib/generators/pu/eject/shell/shell_generator.rb +3 -3
  37. data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
  38. data/lib/generators/pu/gem/letter_opener/letter_opener_generator.rb +21 -0
  39. data/lib/generators/pu/gem/redis/redis_generator.rb +0 -2
  40. data/lib/generators/pu/gem/standard/standard_generator.rb +19 -0
  41. data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -1
  42. data/lib/generators/pu/res/conn/conn_generator.rb +1 -1
  43. data/lib/plutonium/core/controllers/authorizable.rb +1 -1
  44. data/lib/plutonium/definition/actions.rb +6 -2
  45. data/lib/plutonium/definition/base.rb +1 -0
  46. data/lib/plutonium/definition/nested_inputs.rb +19 -0
  47. data/lib/plutonium/railtie.rb +0 -10
  48. data/lib/plutonium/resource/controller.rb +1 -1
  49. data/lib/plutonium/resource/controllers/crud_actions.rb +1 -1
  50. data/lib/plutonium/resource/controllers/interactive_actions.rb +1 -1
  51. data/lib/plutonium/resource/controllers/presentable.rb +1 -5
  52. data/lib/plutonium/resource/policy.rb +4 -5
  53. data/lib/plutonium/resource/register.rb +3 -0
  54. data/lib/plutonium/ui/action_button.rb +34 -19
  55. data/lib/plutonium/ui/block.rb +13 -0
  56. data/lib/plutonium/ui/component/kit.rb +10 -0
  57. data/lib/plutonium/ui/display/resource.rb +29 -11
  58. data/lib/plutonium/ui/display/theme.rb +1 -1
  59. data/lib/plutonium/ui/dyna_frame/content.rb +2 -2
  60. data/lib/plutonium/ui/dyna_frame/host.rb +20 -0
  61. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +282 -0
  62. data/lib/plutonium/ui/form/resource.rb +40 -29
  63. data/lib/plutonium/ui/form/theme.rb +1 -1
  64. data/lib/plutonium/ui/frame_navigator_panel.rb +53 -0
  65. data/lib/plutonium/ui/panel.rb +63 -0
  66. data/lib/plutonium/ui/skeleton_table.rb +29 -0
  67. data/lib/plutonium/ui/table/components/search_bar.rb +1 -1
  68. data/lib/plutonium/ui/table/resource.rb +1 -1
  69. data/lib/plutonium/version.rb +1 -1
  70. data/package-lock.json +5767 -1851
  71. data/package.json +10 -4
  72. data/src/js/controllers/frame_navigator_controller.js +25 -8
  73. data/src/js/controllers/nested_resource_form_fields_controller.js +2 -2
  74. data/src/js/core.js +0 -1
  75. data/tailwind.options.js +89 -11
  76. metadata +36 -12
  77. data/app/assets/plutonium-original.png +0 -0
  78. data/app/assets/plutonium-white.png +0 -0
  79. data/lib/generators/pu/gem/redis/templates/.keep +0 -0
  80. data/public/plutonium-assets/fonts/bootstrap-icons.woff +0 -0
  81. data/public/plutonium-assets/fonts/bootstrap-icons.woff2 +0 -0
  82. /data/{templates → docs/public/templates}/base.rb +0 -0
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Form
6
+ module Concerns
7
+ # Handles rendering of nested resource fields in forms
8
+ # @api private
9
+ module RendersNestedResourceFields
10
+ extend ActiveSupport::Concern
11
+
12
+ DEFAULT_NESTED_LIMIT = 10
13
+ NESTED_OPTION_KEYS = [:allow_destroy, :update_only, :macro, :class].freeze
14
+ SINGULAR_MACROS = %i[belongs_to has_one].freeze
15
+
16
+ class NestedInputsDefinition
17
+ include Plutonium::Definition::DefineableProps
18
+
19
+ defineable_props :field, :input
20
+ end
21
+
22
+ # Template object for new nested records
23
+ class NotPersisted
24
+ def persisted?
25
+ false
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # Renders a nested resource field with associated inputs
32
+ # @param [Symbol] name The name of the nested resource field
33
+ # @raise [ArgumentError] if the nested input definition is missing required configuration
34
+ def render_nested_resource_field(name)
35
+ context = NestedFieldContext.new(
36
+ name: name,
37
+ definition: build_nested_definition(name),
38
+ resource_class: resource_class,
39
+ resource_definition: resource_definition
40
+ )
41
+
42
+ render_nested_field_container(context) do
43
+ render_nested_field_header(context)
44
+ render_nested_field_content(context)
45
+ render_nested_add_button(context)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ class NestedFieldContext
52
+ attr_reader :name, :definition, :options, :permitted_fields
53
+
54
+ def initialize(name:, definition:, resource_class:, resource_definition:)
55
+ @name = name
56
+ @definition = definition
57
+ @resource_definition = resource_definition
58
+ @resource_class = resource_class
59
+ @options = build_options
60
+ @permitted_fields = build_permitted_fields
61
+ end
62
+
63
+ def nested_attribute_options
64
+ @nested_attribute_options ||= @resource_class.all_nested_attributes_options[@name] || {}
65
+ end
66
+
67
+ def nested_input_param
68
+ @options[:as] || :"#{@name}_attributes"
69
+ end
70
+
71
+ def multiple?
72
+ @options[:multiple]
73
+ end
74
+
75
+ private
76
+
77
+ def build_options
78
+ options = @resource_definition.defined_nested_inputs[@name][:options].dup || {}
79
+ merge_nested_options(options)
80
+ set_nested_limits(options)
81
+ options
82
+ end
83
+
84
+ def merge_nested_options(options)
85
+ NESTED_OPTION_KEYS.each do |key|
86
+ options.fetch(key) { options[key] = nested_attribute_options[key] }
87
+ end
88
+ end
89
+
90
+ def set_nested_limits(options)
91
+ options.fetch(:limit) do
92
+ options[:limit] = if SINGULAR_MACROS.include?(nested_attribute_options[:macro])
93
+ 1
94
+ else
95
+ nested_attribute_options[:limit] || DEFAULT_NESTED_LIMIT
96
+ end
97
+ end
98
+
99
+ options.fetch(:multiple) do
100
+ options[:multiple] = !SINGULAR_MACROS.include?(nested_attribute_options[:macro])
101
+ end
102
+ end
103
+
104
+ def build_permitted_fields
105
+ @options[:fields] || @definition.defined_inputs.keys
106
+ end
107
+ end
108
+
109
+ def build_nested_definition(name)
110
+ nested_input_definition = resource_definition.defined_nested_inputs[name]
111
+
112
+ if nested_input_definition[:options]&.fetch(:using, nil)
113
+ nested_input_definition[:options][:using]
114
+ elsif nested_input_definition[:block]
115
+ build_definition_from_block(nested_input_definition[:block])
116
+ else
117
+ raise_missing_nested_definition_error(name)
118
+ end
119
+ end
120
+
121
+ def build_definition_from_block(block)
122
+ definition = NestedInputsDefinition.new
123
+ block.call(definition)
124
+ definition
125
+ end
126
+
127
+ def render_nested_field_container(context, &)
128
+ div(
129
+ class: "col-span-full space-y-2 my-4",
130
+ data: {
131
+ controller: "nested-resource-form-fields",
132
+ nested_resource_form_fields_limit_value: context.options[:limit]
133
+ },
134
+ &
135
+ )
136
+ end
137
+
138
+ def render_nested_field_header(context)
139
+ div do
140
+ h2(class: "text-lg font-semibold text-gray-900 dark:text-white") { context.name.to_s.humanize }
141
+ render_description(context.options[:description]) if context.options[:description]
142
+ end
143
+ end
144
+
145
+ def render_description(description)
146
+ p(class: "text-md font-normal text-gray-500 dark:text-gray-400") { description }
147
+ end
148
+
149
+ def render_nested_field_content(context)
150
+ if context.multiple?
151
+ render_multiple_nested_fields(context)
152
+ else
153
+ render_single_nested_field(context)
154
+ end
155
+
156
+ div(data_nested_resource_form_fields_target: :target, hidden: true)
157
+ end
158
+
159
+ def render_multiple_nested_fields(context)
160
+ render_template_for_nested_fields(context, collection: {NEW_RECORD: NotPersisted.new})
161
+ render_existing_nested_fields(context)
162
+ end
163
+
164
+ def render_single_nested_field(context)
165
+ render_template_for_nested_fields(context, object: NotPersisted.new)
166
+ render_existing_nested_fields(context, single: true)
167
+ end
168
+
169
+ def render_template_for_nested_fields(context, field_options)
170
+ template_tag data_nested_resource_form_fields_target: "template" do
171
+ nesting_method = field_options[:collection] ? :nest_many : :nest_one
172
+ send(nesting_method, context.name, as: context.nested_input_param, template: true, **field_options) do |nested|
173
+ render_fieldset(nested, context)
174
+ end
175
+ end
176
+ end
177
+
178
+ def render_existing_nested_fields(context, single: false)
179
+ nesting_method = single ? :nest_one : :nest_many
180
+ send(nesting_method, context.name, as: context.nested_input_param) do |nested|
181
+ render_fieldset(nested, context)
182
+ end
183
+ end
184
+
185
+ def render_fieldset(nested, context)
186
+ fieldset(
187
+ data_new_record: !nested.object&.persisted?,
188
+ class: "nested-resource-form-fields border border-gray-200 dark:border-gray-700 rounded-lg p-4 space-y-4 relative"
189
+ ) do
190
+ render_fieldset_content(nested, context)
191
+ render_delete_button(nested, context.options)
192
+ end
193
+ end
194
+
195
+ def render_fieldset_content(nested, context)
196
+ div(class: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4 grid-flow-row-dense") do
197
+ render_hidden_fields(nested, context)
198
+ render_input_fields(nested, context)
199
+ end
200
+ end
201
+
202
+ def render_hidden_fields(nested, context)
203
+ if !context.options[:update_only] && context.options[:class]&.respond_to?(:primary_key)
204
+ render nested.field(context.options[:class].primary_key).hidden_tag
205
+ end
206
+ render nested.field(:_destroy).hidden_tag if context.options[:allow_destroy]
207
+ end
208
+
209
+ def render_input_fields(nested, context)
210
+ context.permitted_fields.each do |input|
211
+ render_simple_resource_field(input, context.definition, nested)
212
+ end
213
+ end
214
+
215
+ def render_delete_button(nested, options)
216
+ return unless !nested.object&.persisted? || options[:allow_destroy]
217
+
218
+ render_delete_button_content
219
+ end
220
+
221
+ def render_delete_button_content
222
+ div(class: "flex items-center justify-end") do
223
+ label(class: "inline-flex items-center text-md font-medium text-red-900 cursor-pointer") do
224
+ plain "Delete"
225
+ render_delete_checkbox
226
+ end
227
+ end
228
+ end
229
+
230
+ def render_delete_checkbox
231
+ input(
232
+ type: :checkbox,
233
+ class: "w-4 h-4 ms-2 text-red-600 bg-red-100 border-red-300 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer",
234
+ data_action: "nested-resource-form-fields#remove"
235
+ )
236
+ end
237
+
238
+ def render_nested_add_button(context)
239
+ div do
240
+ button(
241
+ type: :button,
242
+ class: "inline-block",
243
+ data: {
244
+ action: "nested-resource-form-fields#add",
245
+ nested_resource_form_fields_target: "addButton"
246
+ }
247
+ ) do
248
+ render_add_button_content(context.name)
249
+ end
250
+ end
251
+ end
252
+
253
+ def render_add_button_content(name)
254
+ span(class: "bg-secondary-700 text-white hover:bg-secondary-800 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700 dark:focus:ring-secondary-800 flex items-center justify-center px-4 py-1.5 text-sm font-medium rounded-lg focus:outline-none focus:ring-4") do
255
+ render Phlex::TablerIcons::Plus.new(class: "w-4 h-4 mr-1")
256
+ span { "Add #{name.to_s.singularize.humanize}" }
257
+ end
258
+ end
259
+
260
+ def raise_missing_nested_definition_error(name)
261
+ raise ArgumentError, %(
262
+ `nested_input :#{name}` is missing a definition
263
+
264
+ you can either pass in a block:
265
+ ```ruby
266
+ nested_input :#{name} do |definition|
267
+ input :city
268
+ input :country
269
+ end
270
+ ```
271
+
272
+ or pass in options:
273
+ ```ruby
274
+ nested_input :#{name}, using: #{name.to_s.classify}Definition, fields: %i[city country]
275
+ ```
276
+ )
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -4,6 +4,8 @@ module Plutonium
4
4
  module UI
5
5
  module Form
6
6
  class Resource < Base
7
+ include Plutonium::UI::Form::Concerns::RendersNestedResourceFields
8
+
7
9
  attr_reader :resource_fields, :resource_definition
8
10
 
9
11
  def initialize(*, resource_fields:, resource_definition:, **, &)
@@ -35,39 +37,48 @@ module Plutonium
35
37
 
36
38
  def render_resource_field(name)
37
39
  when_permitted(name) do
38
- # field :name, as: :string
39
- # input :name, as: :string
40
- # input :description, class: "col-span-full"
41
- # input :age, tag: {class: "max-h-fit"}
42
- # input :dob do |f|
43
- # f.date_tag
44
- # end
45
-
46
- field_options = resource_definition.defined_fields[name] ? resource_definition.defined_fields[name][:options] : {}
47
-
48
- input_definition = resource_definition.defined_inputs[name] || {}
49
- input_options = input_definition[:options] || {}
50
-
51
- tag = field_options[:as] || input_options[:as]
52
- tag_attributes = input_options[:tag] || {}
53
- tag_block = input_definition[:block] || ->(f) {
54
- tag ||= f.inferred_field_component
55
- f.send(:"#{tag}_tag", **tag_attributes)
56
- }
57
-
58
- field_options = field_options.except(:as)
59
- wrapper_options = input_options.except(:tag, :as)
60
- if !wrapper_options[:class] || wrapper_options[:class].include?("col-span")
61
- # temp hack to allow col span overrides
62
- # TODO: remove once we complete theming, which will support merges
63
- wrapper_options[:class] = tokens("col-span-full", wrapper_options[:class])
64
- end
65
- render field(name, **field_options).wrapped(**wrapper_options) do |f|
66
- render tag_block.call(f)
40
+ if resource_definition.defined_nested_inputs[name]
41
+ render_nested_resource_field(name)
42
+ else
43
+ render_simple_resource_field(name, resource_definition, self)
67
44
  end
68
45
  end
69
46
  end
70
47
 
48
+ def render_simple_resource_field(name, definition, form)
49
+ # field :name, as: :string
50
+ # input :name, as: :string
51
+ # input :description, class: "col-span-full"
52
+ # input :age, tag: {class: "max-h-fit"}
53
+ # input :dob do |f|
54
+ # f.date_tag
55
+ # end
56
+
57
+ field_options = definition.defined_fields[name] ? definition.defined_fields[name][:options] : {}
58
+
59
+ input_definition = definition.defined_inputs[name] || {}
60
+ input_options = input_definition[:options] || {}
61
+
62
+ tag = field_options[:as] || input_options[:as]
63
+ tag_attributes = input_options[:tag] || {}
64
+ tag_block = input_definition[:block] || ->(f) {
65
+ tag ||= f.inferred_field_component
66
+ f.send(:"#{tag}_tag", **tag_attributes)
67
+ }
68
+
69
+ field_options = field_options.except(:as)
70
+ wrapper_options = input_options.except(:tag, :as)
71
+ if !wrapper_options[:class] || !wrapper_options[:class].include?("col-span")
72
+ # temp hack to allow col span overrides
73
+ # TODO: remove once we complete theming, which will support merges
74
+ wrapper_options[:class] = tokens("col-span-full", wrapper_options[:class])
75
+ end
76
+
77
+ render form.field(name, **field_options).wrapped(**wrapper_options) do |f|
78
+ render tag_block.call(f)
79
+ end
80
+ end
81
+
71
82
  def when_permitted(name, &)
72
83
  return unless resource_fields.include? name
73
84
 
@@ -7,7 +7,7 @@ module Plutonium
7
7
  def self.theme
8
8
  super.merge({
9
9
  base: "relative bg-white dark:bg-gray-800 shadow-md sm:rounded-lg my-3 p-6 space-y-6",
10
- fields_wrapper: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-6 grid-flow-row-dense",
10
+ fields_wrapper: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4 grid-flow-row-dense",
11
11
  actions_wrapper: "flex justify-end space-x-2",
12
12
  wrapper: nil,
13
13
  inner_wrapper: "w-full",
@@ -0,0 +1,53 @@
1
+ module Plutonium
2
+ module UI
3
+ class FrameNavigatorPanel < Plutonium::UI::Component::Base
4
+ class PanelItem < Plutonium::UI::Component::Base
5
+ def initialize(label:, icon:, **attributes)
6
+ @label = label
7
+ @icon = icon
8
+ @attributes = attributes
9
+ end
10
+
11
+ def view_template
12
+ button(
13
+ title: @label,
14
+ style: "display: none",
15
+ class: "text-gray-600 dark:text-gray-300",
16
+ **@attributes
17
+ ) {
18
+ render @icon.new(class: "w-6 h-6")
19
+ }
20
+ end
21
+ end
22
+
23
+ class PanelContent < Plutonium::UI::Component::Base
24
+ def initialize(src:)
25
+ @src = src
26
+ end
27
+
28
+ def view_template
29
+ DynaFrameHost src: @src, loading: :lazy, data: {"frame-navigator-target": "frame"} do
30
+ SkeletonTable()
31
+ end
32
+ end
33
+ end
34
+
35
+ def initialize(title:, src:)
36
+ @title = title
37
+ @src = src
38
+ end
39
+
40
+ def view_template
41
+ div(data: {controller: %w[has-many-panel frame-navigator]}) do
42
+ Panel do |panel|
43
+ panel.with_title @title
44
+ panel.with_item PanelItem.new(label: "Home", icon: Phlex::TablerIcons::Home2, data_frame_navigator_target: %(homeButton))
45
+ panel.with_item PanelItem.new(label: "Back", icon: Phlex::TablerIcons::ChevronLeft, data_frame_navigator_target: %(backButton))
46
+ panel.with_item PanelItem.new(label: "Refresh", icon: Phlex::TablerIcons::RefreshDot, data_frame_navigator_target: %(refreshButton))
47
+ panel.with_content PanelContent.new(src: @src)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,63 @@
1
+ module Plutonium
2
+ module UI
3
+ class Panel < Plutonium::UI::Component::Base
4
+ include Phlex::DeferredRender
5
+
6
+ def initialize
7
+ @items = []
8
+ end
9
+
10
+ def with_title(title)
11
+ @title = title
12
+ end
13
+
14
+ def with_item(item)
15
+ @items << item
16
+ end
17
+
18
+ def with_content(content)
19
+ @content = content
20
+ end
21
+
22
+ def view_template
23
+ wrapped do
24
+ render_toolbar if render_toolbar?
25
+ render_content if render_content?
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def wrapped(&)
32
+ div(class: "mt-6", &)
33
+ end
34
+
35
+ def render_toolbar
36
+ div(class: %(flex justify-between items-center mb-4)) do
37
+ if @title.present?
38
+ h5(class: %(text-2xl font-bold tracking-tight text-gray-900 dark:text-white)) do
39
+ @title
40
+ end
41
+ end
42
+ div(class: "flex space-x-4") do
43
+ @items.each do |item|
44
+ render item
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def render_content
51
+ render @content
52
+ end
53
+
54
+ def render_toolbar?
55
+ @title || @items.present?
56
+ end
57
+
58
+ def render_content?
59
+ @content
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ module Plutonium
2
+ module UI
3
+ class SkeletonTable < Plutonium::UI::Component::Base
4
+ def view_template
5
+ div(
6
+ role: "status",
7
+ class:
8
+ "p-4 space-y-4 border border-gray-200 divide-y divide-gray-200 rounded shadow motion-safe:animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700"
9
+ ) do
10
+ div(class: "flex items-center justify-between") do
11
+ div do
12
+ div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5")
13
+ div(class: "w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700")
14
+ end
15
+ div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12")
16
+ end
17
+ div(class: "flex items-center justify-between pt-4") do
18
+ div do
19
+ div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5")
20
+ div(class: "w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700")
21
+ end
22
+ div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12")
23
+ end
24
+ span(class: "sr-only") { "Loading..." }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -16,7 +16,7 @@ module Plutonium
16
16
  private
17
17
 
18
18
  def render?
19
- current_query_object.filter_definitions.present? && current_policy.allowed_to?(:search?)
19
+ (current_query_object.search_filter.present? || current_query_object.filter_definitions.present?) && current_policy.allowed_to?(:search?)
20
20
  end
21
21
  end
22
22
  end
@@ -94,7 +94,7 @@ module Plutonium
94
94
  end
95
95
 
96
96
  def render_footer
97
- div(class: "sticky bottom-[-2px] p-4 pb-6 w-full z-50 bg-gray-50 dark:bg-gray-900") {
97
+ div(class: "sticky bottom-[-2px] mt-1 p-4 pb-6 w-full z-50 bg-gray-50 dark:bg-gray-900") {
98
98
  TableInfo(pagy_instance)
99
99
  TablePagination(pagy_instance)
100
100
  }
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.15.5"
2
+ VERSION = "0.15.7"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0