tramway 0.3.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc95797d7be71f61b7a8350b3ef71905d41c8281d3a134feefc4e8477b910b44
4
- data.tar.gz: 9fc4aa0e0bbec968e96620532082142b3ad48ab701e97ab52c2bd05a8236c047
3
+ metadata.gz: dee256141e588202061885354d7669ee2a5bb1ce99a57a709129031002ab13c8
4
+ data.tar.gz: 36b036fc2890c2710a12684f78ea36f9a2d8ae7e5f11f252973d81ae905f9e3a
5
5
  SHA512:
6
- metadata.gz: ef921bd83394ebde2404a8e5e1b7332af573713553ed73676850955ff8e32f9fc93cdddfa0124658ad40c096557fcd60ed420a55d252f5e5c3fb40ea34cae19e
7
- data.tar.gz: d0a2b72e6e35ea9a297a9af346e794cf1833e8b5922c2e09d98a5f8f06a2d283db49b33753510535a0594365b072aff666712ee50a0b2c9d6036aa530ab52e38
6
+ metadata.gz: b8ee38fd18865625ffdb1c9671cedbba4c780d001b47743f3b0be3657e525eb0d96c84529a9023dc6a68acc6507df1646ca712f4c26dd47df8caadc174c66aa3
7
+ data.tar.gz: 453471063d91beecf588b590008b37546a36b035fed4065499b87f4ef6182ea8fd2a551fcc124f88f1ee07a1ec4df501bfbbafa607a03ea05d1ca28925f5f7c9
data/README.md CHANGED
@@ -8,6 +8,7 @@ Unite Ruby on Rails brilliance. Streamline development with Tramway.
8
8
  * [Tramway Form](https://github.com/Purple-Magic/tramway#tramway-form)
9
9
  * [Tramway Navbar](https://github.com/Purple-Magic/tramway#tramway-navbar)
10
10
  * [Tailwind-styled forms](https://github.com/Purple-Magic/tramway#tailwind-styled-forms)
11
+ * [Tailwind-styled pagination](https://github.com/Purple-Magic/tramway#tailwind-styled-pagination)
11
12
 
12
13
  ## Installation
13
14
  Add this line to your application's Gemfile:
@@ -248,6 +249,24 @@ class Admin::UsersController < Admin::ApplicationController
248
249
  end
249
250
  ```
250
251
 
252
+ ### Form inheritance
253
+
254
+ Tramway Form supports inheritance of `properties`
255
+
256
+ **Example**
257
+
258
+ ```ruby
259
+ class UserForm < TramwayForm
260
+ properties :email, :password
261
+ end
262
+
263
+ class AdminForm < UserForm
264
+ properties :permissions
265
+ end
266
+
267
+ AdminForm.properties # returns [:email, :password, :permissions]
268
+ ```
269
+
251
270
  ### Make flexible and extendable forms
252
271
 
253
272
  Tramway Form properties are not mapped to a model. You're able to make extended forms.
@@ -362,6 +381,32 @@ Available form helpers:
362
381
  * select
363
382
  * submit
364
383
 
384
+ ### Tailwind-styled pagination for Kaminari
385
+
386
+ Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari).
387
+
388
+ #### How to use
389
+
390
+ *Gemfile*
391
+ ```ruby
392
+ gem 'tramway'
393
+ gem 'kaminari'
394
+ ```
395
+
396
+ *config/initializers/tramway.rb*
397
+ ```ruby
398
+ Tramway.configure do |config|
399
+ config.pagination = { enabled: true } # enabled is false by default
400
+ end
401
+ ```
402
+
403
+ *app/views/users/index.html.haml*
404
+ ```haml
405
+ = paginate @users # it will render tailwind-styled pagination buttons by default
406
+ ```
407
+
408
+ Pagination buttons looks like [this](https://play.tailwindcss.com/mqgDS5l9oY)
409
+
365
410
  ## Contributing
366
411
 
367
412
  Install [lefthook](https://github.com/evilmartians/lefthook)
@@ -369,6 +414,7 @@ Install [lefthook](https://github.com/evilmartians/lefthook)
369
414
  ```
370
415
  bundle
371
416
  lefthook install
417
+ rspec
372
418
  ```
373
419
 
374
420
  ## License
@@ -6,9 +6,9 @@ require 'view_component'
6
6
  class TailwindComponent < ViewComponent::Base
7
7
  extend Dry::Initializer[undefined: false]
8
8
 
9
- option :template
9
+ option :input
10
10
  option :attribute
11
- option :object_name
11
+ option :value, optional: true
12
12
  option :options
13
13
  option :label
14
14
  option :for
@@ -6,19 +6,33 @@ module Tailwinds
6
6
  # :reek:InstanceVariableAssumption
7
7
  class Builder < Tramway::Views::FormBuilder
8
8
  def text_field(attribute, **options, &)
9
- render(Tailwinds::Form::TextFieldComponent.new(**default_options(attribute, options)), &)
9
+ render(Tailwinds::Form::TextFieldComponent.new(
10
+ input: input(:text_field),
11
+ value: get_value(attribute, options),
12
+ **default_options(attribute, options)
13
+ ), &)
10
14
  end
11
15
 
12
16
  def password_field(attribute, **options, &)
13
- render(Tailwinds::Form::TextFieldComponent.new(**default_options(attribute, options)), &)
17
+ render(Tailwinds::Form::TextFieldComponent.new(
18
+ input: input(:password_field),
19
+ **default_options(attribute, options)
20
+ ), &)
14
21
  end
15
22
 
16
23
  def file_field(attribute, **options, &)
17
- render(Tailwinds::Form::FileFieldComponent.new(**default_options(attribute, options)), &)
24
+ input = super(attribute, **options.merge(class: :hidden))
25
+
26
+ render(Tailwinds::Form::FileFieldComponent.new(input:, **default_options(attribute, options)), &)
18
27
  end
19
28
 
20
29
  def select(attribute, collection, **options, &)
21
- render(Tailwinds::Form::SelectComponent.new(**default_options(attribute, options).merge(collection:)), &)
30
+ render(Tailwinds::Form::SelectComponent.new(
31
+ input: input(:select),
32
+ value: options[:selected] || object.public_send(attribute),
33
+ collection:,
34
+ **default_options(attribute, options)
35
+ ), &)
22
36
  end
23
37
 
24
38
  def submit(action, **options, &)
@@ -27,11 +41,18 @@ module Tailwinds
27
41
 
28
42
  private
29
43
 
44
+ def input(method_name)
45
+ unbound_method = self.class.superclass.instance_method(method_name)
46
+ unbound_method.bind(self)
47
+ end
48
+
49
+ def get_value(attribute, options)
50
+ options[:value] || object.public_send(attribute)
51
+ end
52
+
30
53
  def default_options(attribute, options)
31
54
  {
32
- template: @template,
33
55
  attribute:,
34
- object_name:,
35
56
  label: label(attribute, options),
36
57
  for: for_id(attribute),
37
58
  options:
@@ -1,4 +1,4 @@
1
1
  .mb-4
2
2
  %label.inline-block.bg-blue-500.hover:bg-blue-700.text-white.font-bold.py-2.px-4.rounded.cursor-pointer.mt-4{ for: @for }
3
3
  = @label
4
- = @template.file_field @object_name, @attribute
4
+ = @input
@@ -4,6 +4,7 @@ module Tailwinds
4
4
  module Form
5
5
  # Tailwind-styled file_field input
6
6
  class FileFieldComponent < TailwindComponent
7
+ option :input
7
8
  end
8
9
  end
9
10
  end
@@ -1,4 +1,4 @@
1
1
  .mb-4
2
2
  %label.block.text-gray-700.text-sm.font-bold.mb-2{ for: @for }
3
3
  = @label
4
- = @template.select @object_name, @attribute, @collection, {}, @options.merge(class: 'bg-white border border-gray-300 text-gray-700 py-2 px-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent')
4
+ = @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: 'bg-white border border-gray-300 text-gray-700 py-2 px-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent'))
@@ -1,4 +1,4 @@
1
1
  .mb-4
2
2
  %label.block.text-gray-700.text-sm.font-bold.mb-2{ for: @for }
3
3
  = @label
4
- = @template.text_field @object_name, @attribute, **@options.merge(class: 'w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-red-500')
4
+ = @input.call @attribute, **@options.merge(class: 'w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-red-500'), value: @value
@@ -0,0 +1,9 @@
1
+ -# Link to the "First" page
2
+ -# available local variables
3
+ -# url: url to the first page
4
+ -# current_page: a page object for the currently displayed page
5
+ -# total_pages: total number of pages
6
+ -# per_page: number of items to fetch per page
7
+ -# remote: data-remote
8
+ %span.first{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
9
+ = link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote
@@ -0,0 +1,8 @@
1
+ -# Non-link tag that stands for skipped pages...
2
+ -# available local variables
3
+ -# current_page: a page object for the currently displayed page
4
+ -# total_pages: total number of pages
5
+ -# per_page: number of items to fetch per page
6
+ -# remote: data-remote
7
+ %span.page.gap{ class: 'px-3 py-2 text-sm font-medium text-purple-700' }
8
+ = t('views.pagination.truncate').html_safe
@@ -0,0 +1,9 @@
1
+ -# Link to the "Last" page
2
+ -# available local variables
3
+ -# url: url to the last page
4
+ -# current_page: a page object for the currently displayed page
5
+ -# total_pages: total number of pages
6
+ -# per_page: number of items to fetch per page
7
+ -# remote: data-remote
8
+ %span.last{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
9
+ = link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote
@@ -0,0 +1,9 @@
1
+ -# Link to the "Next" page
2
+ -# available local variables
3
+ -# url: url to the next page
4
+ -# current_page: a page object for the currently displayed page
5
+ -# total_pages: total number of pages
6
+ -# per_page: number of items to fetch per page
7
+ -# remote: data-remote
8
+ %span.next{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
9
+ = link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote
@@ -0,0 +1,14 @@
1
+ -# Link showing page number
2
+ -# available local variables
3
+ -# page: a page object for "this" page
4
+ -# url: url to this page
5
+ -# current_page: a page object for the currently displayed page
6
+ -# total_pages: total number of pages
7
+ -# per_page: number of items to fetch per page
8
+ -# remote: data-remote
9
+ - if page.current?
10
+ %span{class: "px-3 py-2 font-medium rounded-md bg-purple-500 text-white" }
11
+ = page
12
+ - else
13
+ %span{class: "cursor px-3 py-2 font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100"}
14
+ = link_to_unless page.current?, page, url, {remote: remote, rel: page.rel}
@@ -0,0 +1,12 @@
1
+ = paginator.render do
2
+ %nav.pagination.flex.items-center.justify-center.space-x-1
3
+ = first_page_tag unless current_page.first?
4
+ = prev_page_tag unless current_page.first?
5
+ - each_page do |page|
6
+ - if page.display_tag?
7
+ = page_tag page
8
+ - elsif !page.was_truncated?
9
+ = gap_tag
10
+ = next_page_tag unless current_page.last?
11
+ = last_page_tag unless current_page.last?
12
+
@@ -0,0 +1,9 @@
1
+ -# Link to the "Previous" page
2
+ -# available local variables
3
+ -# url: url to the previous page
4
+ -# current_page: a page object for the currently displayed page
5
+ -# total_pages: total number of pages
6
+ -# per_page: number of items to fetch per page
7
+ -# remote: data-remote
8
+ %span.prev{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
9
+ = link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :reek:ClassVariable { enabled: false }
3
4
  module Tramway
4
5
  # Provides form object for Tramway
5
6
  #
@@ -17,22 +18,40 @@ module Tramway
17
18
  end
18
19
 
19
20
  class << self
20
- def property(attribute, _proc_obj = nil)
21
- @properties ||= []
21
+ def inherited(subclass)
22
+ subclass.instance_variable_set(:@properties, [])
23
+
24
+ super
25
+ end
26
+
27
+ def property(attribute)
22
28
  @properties << attribute
23
29
 
24
30
  delegate attribute, to: :object
25
31
  end
26
32
 
27
33
  def properties(*attributes)
28
- if attributes.any?
29
- attributes.each do |attribute|
30
- property(attribute)
31
- end
32
- else
33
- @properties || []
34
+ attributes.any? ? __set_properties(attributes) : __properties
35
+ end
36
+
37
+ def __set_properties(attributes)
38
+ attributes.each do |attribute|
39
+ property(attribute)
34
40
  end
35
41
  end
42
+
43
+ def __properties
44
+ (__ancestor_properties + @properties).uniq
45
+ end
46
+
47
+ # :reek:ManualDispatch { enabled: false }
48
+ def __ancestor_properties(klass = superclass)
49
+ superklass = klass.superclass
50
+
51
+ return [] unless superklass.respond_to?(:properties)
52
+
53
+ klass.properties + __ancestor_properties(superklass)
54
+ end
36
55
  end
37
56
 
38
57
  def submit(params)
@@ -1,26 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'anyway'
3
4
  require 'singleton'
4
5
  require 'tramway/configs/entity'
5
6
 
6
7
  module Tramway
7
8
  # Basic configuration of Tramway
8
9
  #
9
- class Config
10
+ class Config < Anyway::Config
10
11
  include Singleton
11
12
 
12
- def initialize
13
- @entities = []
14
- end
13
+ attr_config(
14
+ pagination: { enabled: false },
15
+ entities: []
16
+ )
15
17
 
16
18
  def entities=(collection)
17
- @entities = collection.map do |entity|
19
+ super(collection.map do |entity|
18
20
  entity_options = entity.is_a?(Hash) ? entity : { name: entity }
19
21
 
20
22
  Tramway::Configs::Entity.new(**entity_options)
21
- end
23
+ end)
22
24
  end
23
-
24
- attr_reader :entities
25
25
  end
26
26
  end
@@ -7,29 +7,65 @@ module Tramway
7
7
  isolate_namespace Tramway
8
8
 
9
9
  initializer 'tramway.load_helpers' do
10
+ load_navbar_helper
11
+ load_views_helper
12
+ load_decorator_helper
13
+ load_form_helper
14
+ configure_pagination if Tramway.config.pagination[:enabled]
15
+ end
16
+
17
+ private
18
+
19
+ def load_navbar_helper
10
20
  ActiveSupport.on_load(:action_view) do |loaded_class|
11
21
  require 'tramway/helpers/navbar_helper'
12
22
 
13
23
  loaded_class.include Tramway::Helpers::NavbarHelper
14
24
  end
25
+ end
15
26
 
27
+ def load_views_helper
16
28
  ActiveSupport.on_load(:action_view) do |loaded_class|
17
29
  require 'tramway/helpers/views_helper'
18
30
 
19
31
  loaded_class.include Tramway::Helpers::ViewsHelper
20
32
  end
33
+ end
21
34
 
35
+ def load_decorator_helper
22
36
  ActiveSupport.on_load(:action_controller) do |loaded_class|
23
37
  require 'tramway/helpers/decorate_helper'
24
38
 
25
39
  loaded_class.include Tramway::Helpers::DecorateHelper
26
40
  end
41
+ end
27
42
 
43
+ def load_form_helper
28
44
  ActiveSupport.on_load(:action_controller) do |loaded_class|
29
45
  require 'tramway/helpers/form_helper'
30
46
 
31
47
  loaded_class.include Tramway::Helpers::FormHelper
32
48
  end
33
49
  end
50
+
51
+ # :reek:NestedIterators { enabled: false }
52
+ # :reek:TooManyStatements { enabled: false }
53
+ def configure_pagination
54
+ ActiveSupport.on_load(:action_controller) do
55
+ # Detecting tramway views path
56
+ tramway_spec = Gem.loaded_specs['tramway']
57
+ tramway_views_path = File.join(tramway_spec.full_gem_path, 'app/views')
58
+
59
+ paths = view_paths.to_ary
60
+
61
+ # Determine index to insert tramway views path
62
+ rails_views_index = paths.find_index { |path| path.to_s.ends_with?('app/views') }
63
+ insert_index = rails_views_index ? rails_views_index + 1 : 0
64
+
65
+ # Inserting tramway views path
66
+ paths.insert(insert_index, tramway_views_path)
67
+ self.view_paths = paths
68
+ end
69
+ end
34
70
  end
35
71
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '0.3.2'
4
+ VERSION = '0.4.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tramway
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - kalashnikovisme
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-01-04 00:00:00.000000000 Z
12
+ date: 2024-02-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-struct
@@ -106,6 +106,13 @@ files:
106
106
  - app/components/tailwinds/nav/item_component.rb
107
107
  - app/components/tailwinds/navbar_component.html.haml
108
108
  - app/components/tailwinds/navbar_component.rb
109
+ - app/views/kaminari/_first_page.html.haml
110
+ - app/views/kaminari/_gap.html.haml
111
+ - app/views/kaminari/_last_page.html.haml
112
+ - app/views/kaminari/_next_page.html.haml
113
+ - app/views/kaminari/_page.html.haml
114
+ - app/views/kaminari/_paginator.html.haml
115
+ - app/views/kaminari/_prev_page.html.haml
109
116
  - config/routes.rb
110
117
  - lib/rules/turbo_html_attributes_rules.rb
111
118
  - lib/tasks/tramway_tasks.rake