tramway 0.5.2.1 → 0.5.4

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -2
  3. data/app/components/tailwinds/form/builder.rb +8 -0
  4. data/app/components/tailwinds/form/select_component.html.haml +1 -1
  5. data/app/components/tailwinds/form/submit_button_component.html.haml +1 -1
  6. data/app/components/tailwinds/form/text_area_component.html.haml +5 -0
  7. data/app/components/tailwinds/form/text_area_component.rb +9 -0
  8. data/app/components/tailwinds/nav/item_component.rb +1 -1
  9. data/app/components/tailwinds/navbar_component.html.haml +1 -1
  10. data/app/components/tailwinds/table/header_component.html.haml +1 -1
  11. data/app/components/tailwinds/table/row_component.html.haml +4 -16
  12. data/app/components/tailwinds/table/row_component.rb +5 -2
  13. data/app/components/tailwinds/table_component.html.haml +2 -1
  14. data/app/components/tailwinds/table_component.rb +1 -0
  15. data/app/controllers/tramway/entities_controller.rb +10 -1
  16. data/app/helpers/tramway/application_helper.rb +1 -0
  17. data/app/views/tramway/entities/_entity.html.haml +3 -3
  18. data/app/views/tramway/entities/_list.html.haml +8 -5
  19. data/config/routes.rb +16 -15
  20. data/config/tailwind.config.js +12 -0
  21. data/lib/kaminari/helpers/tag.rb +23 -0
  22. data/lib/tramway/base_decorator.rb +9 -0
  23. data/lib/tramway/configs/entities/page.rb +14 -0
  24. data/lib/tramway/configs/entity.rb +11 -4
  25. data/lib/tramway/decorators/association.rb +13 -7
  26. data/lib/tramway/decorators/class_helper.rb +11 -7
  27. data/lib/tramway/forms/properties.rb +17 -1
  28. data/lib/tramway/helpers/decorate_helper.rb +2 -2
  29. data/lib/tramway/helpers/navbar_helper.rb +4 -4
  30. data/lib/tramway/navbar.rb +6 -4
  31. data/lib/tramway/version.rb +1 -1
  32. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7bd63f1711a60db98ddc871375d48d7d3e6eaae3d67626dc038146e42395b3e6
4
- data.tar.gz: ce50bc57610979a11cbc11feb52958b1a76ff85a7e530a28f872d85973f00e7a
3
+ metadata.gz: c3edafce1c744c1e57499e5682ee75f749dbf5bdefd1a9137d70ccaa95f1381f
4
+ data.tar.gz: fbb70ec105b76caad025797238eb09a7e21d5892e90be6b77c1c2858f301c666
5
5
  SHA512:
6
- metadata.gz: e3faa9546ca344e73c22cfa4d751375efadcde87dc28a1767ff47954ca014258ea6d6dd065bdc58c8e6abc76d7bffd739e2116c3a306cb1733c20b44a80e39a9
7
- data.tar.gz: 1f5cc72ffc449a10248be0b997392a9b17ef61bc37b0198ac9a5283963ad40e4428ddc586410b9faf9a04a5f209e96f21747ca2a8649a060ca0ea63250b453e4
6
+ metadata.gz: 4deef971e492a823f04475bc6e915193735844521f8ae65ba8f07da1b1b0edb40e35d944000b648e0c36f1e33d8b2e770b6b62117f52df1a81443fb034132032
7
+ data.tar.gz: fcbea51c9f88495f24110ccac4f473a3a76f2583621fb952ec849fc77c690ee081bcba7a4e789c04f1c77bda02c41dac2d607f7c9b5a6572cdd03172cf31ac0c
data/README.md CHANGED
@@ -8,6 +8,7 @@ Unite Ruby on Rails brilliance. Streamline development with Tramway.
8
8
  * [Tramway Decorators](https://github.com/Purple-Magic/tramway#tramway-decorators)
9
9
  * [Tramway Form](https://github.com/Purple-Magic/tramway#tramway-form)
10
10
  * [Tramway Navbar](https://github.com/Purple-Magic/tramway#tramway-navbar)
11
+ * [Tramway Table Component](https://github.com/Purple-Magic/tramway#tramway-table-component)
11
12
  * [Tailwind-styled forms](https://github.com/Purple-Magic/tramway#tailwind-styled-forms)
12
13
  * [Stimulus-based inputs](https://github.com/Purple-Magic/tramway#stimulus-based-inputs)
13
14
  * [Tailwind-styled pagination](https://github.com/Purple-Magic/tramway?tab=readme-ov-file#tailwind-styled-pagination-for-kaminari)
@@ -26,7 +27,7 @@ gem "view_component"
26
27
  OR
27
28
 
28
29
  ```shell
29
- bundle add tramway view_component
30
+ bundle add tramway view_component kaminari view_component
30
31
  ```
31
32
 
32
33
  ## Getting Started
@@ -194,6 +195,8 @@ end
194
195
 
195
196
  #### Decorate associations
196
197
 
198
+ **Decorate single association**
199
+
197
200
  ```ruby
198
201
  class UserDecorator < Tramway::BaseDecorator
199
202
  association :posts
@@ -203,6 +206,14 @@ user = tramway_decorate User.first
203
206
  user.posts # => decorated collection of posts with PostDecorator
204
207
  ```
205
208
 
209
+ **Decorate multiple associations**
210
+
211
+ ```ruby
212
+ class UserDecorator < Tramway::BaseDecorator
213
+ associations :posts, :users
214
+ end
215
+ ```
216
+
206
217
  #### Decorate nil
207
218
 
208
219
  Tramway Decorator does not decorate nil objects
@@ -225,7 +236,7 @@ Tramway provides **convenient** form objects for Rails applications. List proper
225
236
  class UserForm < Tramway::BaseForm
226
237
  properties :email, :password, :first_name, :last_name, :phone
227
238
 
228
- normalizes :email, ->(value) { value.strip.downcase }
239
+ normalizes :email, with: ->(value) { value.strip.downcase }
229
240
  end
230
241
  ```
231
242
 
@@ -491,6 +502,20 @@ tramway_navbar title: 'Purple Magic' do |nav|
491
502
  end
492
503
  ```
493
504
 
505
+ ### Tramway Table Component
506
+
507
+ Tramway provides a responsive, tailwind-styled table with light and dark themes.
508
+
509
+ ```haml
510
+ = component 'tailwinds/table' do
511
+ = component 'tailwinds/table/header', headers: ['Column 1', 'Column 2']
512
+ = component 'tailwinds/table/row' do
513
+ = component 'tailwinds/table/cell' do
514
+ Something
515
+ = component 'tailwinds/table/cell' do
516
+ Another
517
+ ```
518
+
494
519
  ### Tailwind-styled forms
495
520
 
496
521
  Tramway uses [Tailwind](https://tailwindcss.com/) by default. All UI helpers are implemented with [ViewComponent](https://github.com/viewcomponent/view_component).
@@ -13,6 +13,14 @@ module Tailwinds
13
13
  ), &)
14
14
  end
15
15
 
16
+ def text_area(attribute, **options, &)
17
+ render(Tailwinds::Form::TextAreaComponent.new(
18
+ input: input(:text_area),
19
+ value: get_value(attribute, options),
20
+ **default_options(attribute, options)
21
+ ), &)
22
+ end
23
+
16
24
  def password_field(attribute, **options, &)
17
25
  render(Tailwinds::Form::TextFieldComponent.new(
18
26
  input: input(:password_field),
@@ -2,4 +2,4 @@
2
2
  - if @label
3
3
  = component('tailwinds/form/label', for: @for) do
4
4
  = @label
5
- = @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: 'bg-white border border-gray-300 text-gray-700 py-2.5 px-2 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent'))
5
+ = @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: 'bg-white border border-gray-300 text-gray-700 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent py-2 px-3 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed'))
@@ -1,4 +1,4 @@
1
1
  .flex.items-center.justify-between
2
- %button.bg-red-500.hover:bg-red-700.text-white.font-bold.py-2.px-4.rounded.focus:outline-none.focus:shadow-outline.cursor-pointer{ type: :submit, name: :commit, **@options }
2
+ %button.bg-red-500.hover:bg-red-700.text-white.font-bold.py-2.px-4.rounded.focus:outline-none.focus:shadow-outline.cursor-pointer.dark:text-white{ type: :submit, name: :commit, **@options }
3
3
  = @text
4
4
  = @content
@@ -0,0 +1,5 @@
1
+ .mb-4
2
+ - if @label
3
+ = component('tailwinds/form/label', for: @for) do
4
+ = @label
5
+ = @input.call @attribute, **@options.merge(class: 'w-full bg-white px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-red-500 dark:placeholder-gray-400'), value: @value
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tailwinds
4
+ module Form
5
+ # Tailwind-styled text field
6
+ class TextAreaComponent < TailwindComponent
7
+ end
8
+ end
9
+ end
@@ -8,7 +8,7 @@ module Tailwinds
8
8
  def style
9
9
  @style ||= [
10
10
  'text-white', 'hover:bg-gray-300', 'hover:text-gray-800', 'px-4', 'py-2', 'rounded', 'whitespace-nowrap',
11
- 'dark:hover:bg-gray-700', 'dark:hover:text-gray-400'
11
+ 'dark:hover:bg-gray-700', 'dark:hover:text-gray-400', 'dark:text-white'
12
12
  ].join(' ')
13
13
  end
14
14
  end
@@ -4,7 +4,7 @@
4
4
  .flex.items-center
5
5
  - if @title[:text].present?
6
6
  = link_to @title[:link] do
7
- .text-xl.text-white.font-bold
7
+ .text-xl.text-white.font-bold.dark:text-white
8
8
  = @title[:text]
9
9
  - if @left_items.present?
10
10
  %ul.block.flex.flex-row.items-center.space-x-4.ml-4
@@ -1,4 +1,4 @@
1
- .div-table-row.block.grid.text-white.text-small.gap-4.bg-purple-700.dark:bg-gray-700.dark:text-gray-400{ class: "grid-cols-#{headers.count}" }
1
+ .div-table-row.grid.text-white.text-small.gap-4.bg-purple-700.dark:bg-gray-700.dark:text-gray-400{ class: "grid-cols-#{headers.count}", aria: { label: "Table Header" }, role: "row" }
2
2
  - if headers.any?
3
3
  - headers.each do |header|
4
4
  .div-table-cell.py-4.px-6
@@ -1,27 +1,15 @@
1
1
  - if cells.any?
2
- -# desktop view
3
- = row_tag class: desktop_row_classes(cells.count) do
2
+ = row_tag class: desktop_row_classes(cells.count), **options do
4
3
  - cells.each do |(_, value)|
5
4
  .div-table-cell.px-6.py-4.font-medium.text-gray-900.whitespace-nowrap.dark:text-white.text-xs.sm:text-base
6
5
  = value
7
6
 
8
- -# mobile view
9
- .div-table-row.xl:hidden.border-b.dark:bg-gray-800.dark:border-gray-700.mb-2{ "data-action" => "click->preview#toggle", "data-controller" => "preview", "data-items" => cells.to_json }
10
- .w-full.p-4.bg-purple-100.text-gray-700.dark:bg-gray-700.dark:text-gray-400
11
- = cells.values.first
12
-
13
- .flex.overflow-x-auto.whitespace-nowrap
14
- - cells.each_with_index do |(_, value), index|
15
- - next if index == 0
16
-
17
- .text-gray-900.dark:text-white.p-4.text-xs.sm:text-base.inline-block.w-auto
18
- = value
19
7
  - else
20
- - cells = Nokogiri::HTML.fragment(content).children.css('div')
8
+ - cells = Nokogiri::HTML.fragment(content).xpath('./*[@class and contains(concat(" ", normalize-space(@class), " "), " div-table-cell ")]')
21
9
 
22
10
  - if href.present?
23
- = tag.a href:, class: [desktop_row_classes(cells.count), link_row_classes].join(' ') do
11
+ = tag.a href:, class: [desktop_row_classes(cells.count), link_row_classes].join(' '), **options do
24
12
  = content
25
13
  - else
26
- = tag.div class: desktop_row_classes(cells.count) do
14
+ = tag.div class: desktop_row_classes(cells.count), **options do
27
15
  = content
@@ -6,16 +6,19 @@ module Tailwinds
6
6
  class RowComponent < Tramway::Component::Base
7
7
  option :cells, optional: true, default: -> { [] }
8
8
  option :href, optional: true
9
+ option :options, optional: true, default: -> { {} }
9
10
 
10
11
  def row_tag(**options, &)
12
+ default_attributes = { role: :row }
13
+
11
14
  if href.present?
12
15
  klass = "#{options[:class] || ''} #{link_row_classes}"
13
16
 
14
- link_to(href, options.merge(class: klass)) do
17
+ link_to(href, options.merge(class: klass, **default_attributes)) do
15
18
  yield if block_given?
16
19
  end
17
20
  else
18
- tag.div(**options) do
21
+ tag.div(**options.merge(default_attributes)) do
19
22
  yield if block_given?
20
23
  end
21
24
  end
@@ -1,4 +1,5 @@
1
1
  = helpers.component 'tailwinds/table/row/preview'
2
2
 
3
- .div-table.w-full.text-left.rtl:text-right.text-gray-500.dark:text-gray-400.mt-4
3
+ - width_class = options[:class]&.include?('w-') ? '' : 'w-full'
4
+ .div-table.text-left.rtl:text-right.text-gray-500.dark:text-gray-400.mt-4{ class: "#{options[:class]} #{width_class}", **options.except(:class) }
4
5
  = content
@@ -3,5 +3,6 @@
3
3
  module Tailwinds
4
4
  # Table component for rendering a table
5
5
  class TableComponent < Tramway::Component::Base
6
+ option :options, optional: true, default: -> { {} }
6
7
  end
7
8
  end
@@ -11,7 +11,12 @@ module Tramway
11
11
  include Rails.application.routes.url_helpers
12
12
 
13
13
  def index
14
- @entities = model_class.page(params[:page])
14
+ @entities = if entity.page(:index).scope.present?
15
+ model_class.public_send(entity.page(:index).scope)
16
+ else
17
+ model_class.order(id: :desc)
18
+ end.page(params[:page])
19
+ @namespace = entity.route.namespace
15
20
  end
16
21
 
17
22
  private
@@ -19,5 +24,9 @@ module Tramway
19
24
  def model_class
20
25
  @model_class ||= params[:entity][:name].classify.constantize
21
26
  end
27
+
28
+ def entity
29
+ @entity ||= Tramway.config.entities.find { |e| e.name == params[:entity][:name] }
30
+ end
22
31
  end
23
32
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tramway/helpers/component_helper'
4
+ require 'kaminari/helpers/tag'
4
5
 
5
6
  module Tramway
6
7
  # Main helper module for Tramway entities pages
@@ -1,3 +1,3 @@
1
- - decorator = decorator_class(entity)
2
- - decorated_object = decorator.decorate(entity)
3
- = component 'tailwinds/table/row', cells: decorator.list_attributes.reduce({}) { |hash, attribute| hash.merge! attribute => decorated_object.public_send(attribute) }, href: decorated_object.show_path
1
+ - decorator = decorator_class_name(entity, @namespace)
2
+ - decorated_object = decorator.constantize.decorate(entity)
3
+ = component 'tailwinds/table/row', cells: decorator.constantize.list_attributes.reduce({}) { |hash, attribute| hash.merge! attribute => decorated_object.public_send(attribute) }, href: decorated_object.show_path
@@ -1,15 +1,17 @@
1
- - decorator = Tramway::Decorators::NameBuilder.default_decorator_class_name(@model_class)
1
+ - decorator = Tramway::Decorators::ClassHelper.decorator_class_name(@model_class, @namespace)
2
2
  - list_attributes = decorator.constantize.list_attributes
3
3
 
4
4
  .mt-8.w-full
5
5
  - content_for :title, page_title
6
6
 
7
- .flex.justify-between.items-center.md:mt-4.mt-2
7
+ .flex.justify-between.items-center
8
8
  %h1.font-bold.text-4xl.dark:text-white
9
9
  = content_for(:title)
10
10
 
11
11
  - if Tramway.config.pagination[:enabled]
12
- = paginate @entities
12
+ = paginate @entities, custom_path_method: "#{@model_class.model_name.plural}_path"
13
+ .flex.justify-end.mt-2
14
+ = decorator.constantize.index_header_content.call(@entities) if decorator.constantize.index_header_content.present?
13
15
 
14
16
  - if list_attributes.empty?
15
17
  %p.text-center.mt-10
@@ -21,5 +23,6 @@
21
23
  - @entities.each do |item|
22
24
  = render 'tramway/entities/entity', entity: item
23
25
 
24
- .flex.mt-4
25
- = paginate @entities
26
+ - if Tramway.config.pagination[:enabled]
27
+ .flex.mt-4
28
+ = paginate @entities, custom_path_method: "#{@model_class.model_name.plural}_path"
data/config/routes.rb CHANGED
@@ -2,29 +2,30 @@
2
2
 
3
3
  Tramway::Engine.routes.draw do
4
4
  Tramway.config.entities.each do |entity|
5
- define_resource = lambda do |resource_name, entity| # rubocop:disable Lint/ShadowingOuterLocalVariable
5
+ segments = entity.name.split('/')
6
+ resource_name = segments.pop
7
+
8
+ define_resource = proc do
6
9
  resources resource_name.pluralize.to_sym,
7
- only: [:index],
8
- controller: '/tramway/entities',
9
- defaults: { entity: }
10
+ only: [:index],
11
+ controller:'/tramway/entities',
12
+ defaults: { entity: entity }
10
13
  end
11
14
 
12
- if entity.name.include?('/')
13
- *namespaces, resource_name = entity.name.split('/')
14
-
15
- define_namespace = lambda do |index|
16
- namespace namespaces[index].to_sym do
17
- if namespaces[index] == namespaces.last
18
- define_resource.call(resource_name, entity)
15
+ if segments.empty?
16
+ define_resource.call
17
+ else
18
+ nest = lambda do |names|
19
+ namespace names.first.to_sym do
20
+ if names.size > 1
21
+ nest.call(names.drop(1))
19
22
  else
20
- define_namespace.call(namespaces[index + 1])
23
+ define_resource.call
21
24
  end
22
25
  end
23
26
  end
24
27
 
25
- define_namespace.call(0)
26
- else
27
- define_resource.call(entity.name, entity)
28
+ nest.call(segments)
28
29
  end
29
30
  end
30
31
  end
@@ -23,7 +23,11 @@ module.exports = {
23
23
  'flex',
24
24
  'bg-purple-700',
25
25
  'px-6',
26
+ 'px-3',
27
+ 'px-4',
26
28
  'py-4',
29
+ 'py-2',
30
+ 'mb-2',
27
31
  'dark:placeholder-gray-400',
28
32
  'dark:text-white',
29
33
  'dark:bg-gray-800',
@@ -41,6 +45,14 @@ module.exports = {
41
45
  'hover:bg-gray-100',
42
46
  'hover:bg-gray-300',
43
47
  'hover:text-gray-800',
48
+ 'mt-8',
49
+ 'justify-between',
50
+ 'space-x-1',
51
+ 'justify-end',
52
+ 'mt-2',
53
+ 'disabled:bg-gray-100',
54
+ 'disabled:text-gray-400',
55
+ 'disabled:cursor-not-allowed',
44
56
  // pagination
45
57
  'bg-white', 'rounded-md', 'hover:bg-purple-100', 'dark:text-white', 'dark:bg-gray-800', 'dark:hover:bg-gray-700',
46
58
  // multiselect styles
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaminari
4
+ module Helpers
5
+ # Monkey patch for Kaminari::Helpers::Tag to support :custom_path_method
6
+ # :reek:InstanceVariableAssumption { enabled: false }
7
+ class Tag
8
+ def page_url_for(page)
9
+ custom_path_method = @options[:custom_path_method]
10
+
11
+ if custom_path_method.present?
12
+ Tramway::Engine.routes.url_helpers.public_send(
13
+ custom_path_method,
14
+ @params.except(:controller, :action).merge(page: page)
15
+ )
16
+ else
17
+ params = params_for(page)
18
+ @template.url_for params.merge(only_path: true)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,6 +4,7 @@ require 'tramway/decorators/name_builder'
4
4
  require 'tramway/decorators/association'
5
5
  require 'tramway/decorators/collection_decorator'
6
6
  require 'tramway/helpers/decorate_helper'
7
+ require 'tramway/helpers/component_helper'
7
8
  require 'tramway/utils/render'
8
9
  require 'tramway/duck_typing'
9
10
 
@@ -15,6 +16,7 @@ module Tramway
15
16
  include Tramway::Utils::Render
16
17
  include Tramway::DuckTyping::ActiveRecordCompatibility
17
18
  include Tramway::Helpers::DecorateHelper
19
+ include Tramway::Helpers::ComponentHelper
18
20
 
19
21
  attr_reader :object
20
22
 
@@ -23,6 +25,9 @@ module Tramway
23
25
  end
24
26
 
25
27
  class << self
28
+ include Tramway::Helpers::ComponentHelper
29
+ include Tramway::Utils::Render
30
+
26
31
  # :reek:NilCheck { enabled: false } because checking for nil is not a type-checking issue but business logic
27
32
  def decorate(object_or_array)
28
33
  return if object_or_array.nil?
@@ -47,6 +52,10 @@ module Tramway
47
52
  []
48
53
  end
49
54
 
55
+ def index_header_content
56
+ nil
57
+ end
58
+
50
59
  include Tramway::Decorators::AssociationClassMethods
51
60
  end
52
61
 
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Configs
5
+ module Entities
6
+ # Route struct describes rules for route management
7
+ #
8
+ class Page < Dry::Struct
9
+ attribute :action, Types::Coercible::String
10
+ attribute? :scope, Types::Coercible::String
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tramway/configs/entities/route'
4
+ require 'tramway/configs/entities/page'
4
5
 
5
6
  module Tramway
6
7
  module Configs
7
8
  # Tramway is an entity-based framework
8
9
  class Entity < Dry::Struct
9
10
  attribute :name, Types::Coercible::String
10
- attribute? :pages, Types::Array.of(Types::Symbol).default([].freeze)
11
+ attribute? :pages, Types::Array.of(Tramway::Configs::Entities::Page).default([].freeze)
11
12
  attribute? :route, Tramway::Configs::Entities::Route
12
13
 
13
14
  # Route Struct contains implemented in Tramway CRUD and helpful routes for the entity
14
15
  RouteStruct = Struct.new(:index)
15
16
 
16
- # HumanNameStruct contains human names forms for the entity
17
+ # HumanName Struct contains human names forms for the entity
17
18
  HumanNameStruct = Struct.new(:single, :plural)
18
19
 
19
20
  def routes
@@ -31,6 +32,10 @@ module Tramway
31
32
  HumanNameStruct.new(single, plural)
32
33
  end
33
34
 
35
+ def page(name)
36
+ pages.find { |page| page.action == name.to_s }
37
+ end
38
+
34
39
  private
35
40
 
36
41
  def pluralized(model_name)
@@ -48,7 +53,7 @@ module Tramway
48
53
  def route_helper_method
49
54
  underscored_name = name.parameterize.pluralize.underscore
50
55
 
51
- method_name = if pages.include?(:index) || route.blank?
56
+ method_name = if set_page?(:index) || route.blank?
52
57
  "#{underscored_name}_path"
53
58
  else
54
59
  route.helper_method_by(underscored_name)
@@ -58,8 +63,10 @@ module Tramway
58
63
  end
59
64
 
60
65
  def route_helper_engine
61
- pages.include?(:index) ? Tramway::Engine : Rails.application
66
+ set_page?(:index) ? Tramway::Engine : Rails.application
62
67
  end
68
+
69
+ alias set_page? page
63
70
  end
64
71
  end
65
72
  end
@@ -22,14 +22,14 @@ module Tramway
22
22
  end
23
23
 
24
24
  # has_and_belongs_to_many is not supported for now
25
- def association(association)
25
+ def association(association, decorator: nil)
26
26
  define_method(association) do
27
27
  assoc = object.send(association)
28
28
 
29
29
  if assoc.is_a?(ActiveRecord::Relation)
30
- AssocDecoratorHelper.decorate_has_many_association assoc
30
+ AssocDecoratorHelper.decorate_has_many_association assoc, decorator_class: decorator
31
31
  elsif assoc.present?
32
- AssocDecoratorHelper.decorate_associated_object(assoc)
32
+ AssocDecoratorHelper.decorate_associated_object(assoc, decorator_class: decorator)
33
33
  end
34
34
  end
35
35
  end
@@ -38,12 +38,18 @@ module Tramway
38
38
  # Helper module for association decorators
39
39
  module AssocDecoratorHelper
40
40
  class << self
41
- def decorate_has_many_association(assoc)
42
- assoc.empty? ? [] : decorator(assoc.klass).decorate(assoc)
41
+ def decorate_has_many_association(assoc, decorator_class: nil)
42
+ return [] if assoc.empty?
43
+
44
+ decorator_class ||= decorator(assoc.klass)
45
+
46
+ decorator_class.decorate(assoc)
43
47
  end
44
48
 
45
- def decorate_associated_object(assoc)
46
- decorator(assoc.class).decorate(assoc)
49
+ def decorate_associated_object(assoc, decorator_class: nil)
50
+ decorator_class ||= decorator(assoc.class)
51
+
52
+ decorator_class.decorate(assoc)
47
53
  end
48
54
 
49
55
  def decorator(class_name)
@@ -6,27 +6,31 @@ module Tramway
6
6
  module ClassHelper
7
7
  module_function
8
8
 
9
- def decorator_class(object_or_array, decorator = nil)
9
+ def decorator_class(object_or_array, decorator = nil, namespace = nil)
10
10
  raise_error_if_object_empty object_or_array, decorator
11
11
 
12
12
  return decorator if decorator.present?
13
13
 
14
14
  begin
15
- class_name = decorator_class_name(object_or_array)
15
+ class_name = decorator_class_name(object_or_array, namespace)
16
16
  class_name.constantize
17
17
  rescue NameError
18
18
  raise NameError, "You should define #{class_name} decorator class."
19
19
  end
20
20
  end
21
21
 
22
- def decorator_class_name(object_or_array)
23
- klass = if Tramway::Decorators::CollectionDecorators.collection?(object_or_array)
24
- object_or_array.first.class
22
+ def decorator_class_name(object_or_array_or_class, namespace)
23
+ klass = if Tramway::Decorators::CollectionDecorators.collection?(object_or_array_or_class)
24
+ object_or_array_or_class.first.class
25
+ elsif object_or_array_or_class.is_a?(Class)
26
+ object_or_array_or_class
25
27
  else
26
- object_or_array.class
28
+ object_or_array_or_class.class
27
29
  end
28
30
 
29
- Tramway::Decorators::NameBuilder.default_decorator_class_name(klass)
31
+ base_class_name = Tramway::Decorators::NameBuilder.default_decorator_class_name(klass)
32
+
33
+ namespace.present? ? "#{namespace.to_s.camelize}::#{base_class_name}" : base_class_name
30
34
  end
31
35
 
32
36
  # :reek:NilCheck { enabled: false }
@@ -9,7 +9,23 @@ module Tramway
9
9
  def property(attribute)
10
10
  @properties << attribute
11
11
 
12
- delegate attribute, to: :object
12
+ define_method(attribute) do
13
+ if object.respond_to?(attribute)
14
+ object.public_send(attribute)
15
+ else
16
+ raise NoMethodError, "#{self.class}##{attribute} is not defined"
17
+ end
18
+ end
19
+
20
+ set_method = "#{attribute}="
21
+
22
+ define_method(set_method) do |value|
23
+ if object.respond_to?(set_method)
24
+ object.public_send(set_method, value)
25
+ else
26
+ raise NoMethodError, "#{self.class}##{set_method} is not defined"
27
+ end
28
+ end
13
29
  end
14
30
 
15
31
  def properties(*attributes)
@@ -8,12 +8,12 @@ module Tramway
8
8
  #
9
9
  module DecorateHelper
10
10
  # :reek:NilCheck { enabled: false } because checking for nil is not a type-checking issue but business logic
11
- def tramway_decorate(object_or_array, decorator: nil)
11
+ def tramway_decorate(object_or_array, decorator: nil, namespace: nil)
12
12
  return [] if Tramway::Decorators::CollectionDecorators.collection?(object_or_array) && object_or_array.empty?
13
13
 
14
14
  return if object_or_array.nil?
15
15
 
16
- Tramway::Decorators::ClassHelper.decorator_class(object_or_array, decorator).decorate object_or_array
16
+ Tramway::Decorators::ClassHelper.decorator_class(object_or_array, decorator, namespace).decorate object_or_array
17
17
  end
18
18
  end
19
19
  end
@@ -6,8 +6,8 @@ module Tramway
6
6
  module Helpers
7
7
  # Provides navbar helpers for ActionView
8
8
  module NavbarHelper
9
- def tramway_navbar(**options)
10
- initialize_navbar
9
+ def tramway_navbar(with_entities: true, **options)
10
+ initialize_navbar(with_entities:)
11
11
 
12
12
  yield @navbar if block_given?
13
13
 
@@ -18,8 +18,8 @@ module Tramway
18
18
 
19
19
  private
20
20
 
21
- def initialize_navbar
22
- @navbar = Tramway::Navbar.new self
21
+ def initialize_navbar(with_entities:)
22
+ @navbar = Tramway::Navbar.new self, with_entities:
23
23
  end
24
24
 
25
25
  def assign_navbar_items(options)
@@ -5,16 +5,18 @@ module Tramway
5
5
  class Navbar
6
6
  attr_reader :items, :context
7
7
 
8
- def initialize(context)
8
+ def initialize(context, with_entities:)
9
9
  @context = context
10
10
  @items = { left: [], right: [] }
11
11
  @filling = nil
12
12
 
13
- entities = Tramway.config.entities
13
+ if with_entities
14
+ entities = Tramway.config.entities
14
15
 
15
- return unless entities.any?
16
+ return unless entities.any?
16
17
 
17
- preset_left entities
18
+ preset_left entities
19
+ end
18
20
  end
19
21
 
20
22
  def left
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '0.5.2.1'
4
+ VERSION = '0.5.4'
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.5.2.1
4
+ version: 0.5.4
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: 2025-02-18 00:00:00.000000000 Z
12
+ date: 2025-06-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: anyway_config
@@ -162,6 +162,8 @@ files:
162
162
  - app/components/tailwinds/form/select_component.rb
163
163
  - app/components/tailwinds/form/submit_button_component.html.haml
164
164
  - app/components/tailwinds/form/submit_button_component.rb
165
+ - app/components/tailwinds/form/text_area_component.html.haml
166
+ - app/components/tailwinds/form/text_area_component.rb
165
167
  - app/components/tailwinds/form/text_field_component.html.haml
166
168
  - app/components/tailwinds/form/text_field_component.rb
167
169
  - app/components/tailwinds/nav/item/button_component.html.haml
@@ -210,12 +212,14 @@ files:
210
212
  - app/views/tramway/layouts/application.html.haml
211
213
  - config/routes.rb
212
214
  - config/tailwind.config.js
215
+ - lib/kaminari/helpers/tag.rb
213
216
  - lib/rules/turbo_html_attributes_rules.rb
214
217
  - lib/tasks/tramway_tasks.rake
215
218
  - lib/tramway.rb
216
219
  - lib/tramway/base_decorator.rb
217
220
  - lib/tramway/base_form.rb
218
221
  - lib/tramway/config.rb
222
+ - lib/tramway/configs/entities/page.rb
219
223
  - lib/tramway/configs/entities/route.rb
220
224
  - lib/tramway/configs/entity.rb
221
225
  - lib/tramway/decorators/association.rb