tramway 1.1.2.2 → 2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7daf328b2fd26510b8c32822ca6e8f1df91d72569e54f2a182e165f7654ff35e
4
- data.tar.gz: d0bbdc12b3846410dff72d24cc195eb089c3cd3c692fa253c29752490bf7a078
3
+ metadata.gz: 0c779661b04c0ea3be53c76acdae493d703604e1dd3a2f48aad1c2121d6ea7d6
4
+ data.tar.gz: 41f3c46793abc8f3a6059b7be90ac1ef3eed8bebdf6e3c0bf880a8feac548f17
5
5
  SHA512:
6
- metadata.gz: 133c3f109631e45ae41201406fdbee2ac4a18f9996cb4cfd96b8fe2f9e15fe9a620b2283bb47fa031fd9cddb637d6f292d13379924f5b26af9387602d6ba3ff4
7
- data.tar.gz: fe76dc4556c28615eab0fcc894b37f381e665c120e5e3ad113a38bb4b1b825cb80b2b2a7be25c13270b6dceca97c3f629ca7cb62000b55c64debd10d21e1a40b
6
+ metadata.gz: e48c87f877ec8f72bb8554e4983be7f3e85c8845c117a862055db7f97a84cebc8c2e7cdbf272726f5be9f57b7ffed5505cef352482f98d5f503326f93ece247f
7
+ data.tar.gz: 20417c4f44f6e0dd627ff8770dd08473fba1093a517baff5aacfd803e5457baf0f9dde7c8425f4317fc35654fc064e4e28ac4d9fb57e1f0588d8ce2476968a92
data/README.md CHANGED
@@ -786,6 +786,15 @@ Tramway ships with helpers for common UI patterns built on top of Tailwind compo
786
786
 
787
787
  All additional keyword arguments are forwarded to the underlying component as HTML attributes.
788
788
 
789
+ You can also pass HTML attributes for the generated `<form>` separately via `form_options:` while keeping button-specific
790
+ attributes in `options:`:
791
+
792
+ ```erb
793
+ <%= tramway_button path: user_path(user), text: 'Create',
794
+ form_options: { data: { turbo: false } },
795
+ options: { data: { controller: 'submit-once' } } %>
796
+ ```
797
+
789
798
  The `type` option maps semantic intent to Tailwind color families. The full set of supported values is:
790
799
 
791
800
  | Type | Color |
@@ -6,7 +6,7 @@
6
6
  %button{ type: :submit, name: :commit, class: classes, **options.except(:class) }
7
7
  = text
8
8
  - else
9
- = button_to text, path, method:, class: classes, **options.except(:class)
9
+ = button_to text, path, method:, form: form_options, class: classes, **options.except(:class)
10
10
  - else
11
11
  - if link
12
12
  = link_to path, class: classes, **options.except(:class) do
@@ -16,5 +16,5 @@
16
16
  %button{ type: :submit, name: :commit, class: classes, **options.except(:class) }
17
17
  = content
18
18
  - else
19
- = button_to path, method:, class: classes, **options.except(:class) do
19
+ = button_to path, method:, class: classes, form: form_options, **options.except(:class) do
20
20
  = content
@@ -12,6 +12,7 @@ module Tailwinds
12
12
  option :method, optional: true, default: -> { :get }
13
13
  option :link, optional: true, default: -> { false }
14
14
  option :options, optional: true, default: -> { {} }
15
+ option :form_options, optional: true, default: -> { {} }
15
16
 
16
17
  def size_classes
17
18
  {
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tramway/utils/field'
4
+
3
5
  module Tailwinds
4
6
  module Form
5
7
  # Provides Tailwind-styled forms
6
- # :reek:InstanceVariableAssumption
7
8
  class Builder < Tramway::Views::FormBuilder
9
+ include Tramway::Utils::Field
10
+
8
11
  def initialize(object_name, object, template, options)
9
12
  super
10
13
 
@@ -105,8 +108,6 @@ module Tailwinds
105
108
  { attribute:, label: label_build(attribute, options), for: for_id(attribute), options:, size: form_size }
106
109
  end
107
110
 
108
- # :reek:UtilityFunction
109
- # :reek:NilCheck
110
111
  def label_build(attribute, options)
111
112
  label_option = options[:label]
112
113
 
@@ -125,7 +126,6 @@ module Tailwinds
125
126
  end
126
127
 
127
128
  # REMOVE IT. WE MUST UNDERSTAND WHY INCLUDE_BLANK DOES NOT WORK
128
- # :reek:UtilityFunction
129
129
  def explicitly_add_blank_option(collection, options)
130
130
  if options[:include_blank]
131
131
  collection = collection.to_a if collection.is_a? Hash
@@ -0,0 +1,5 @@
1
+ %h1.font-bold.text-4xl.dark:text-white
2
+ - if text.present?
3
+ = text
4
+ - else
5
+ = content
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tailwinds
4
+ # Title component
5
+ class TitleComponent < Tramway::BaseComponent
6
+ option :text
7
+ end
8
+ end
@@ -31,6 +31,22 @@ module Tramway
31
31
  set_associations
32
32
  end
33
33
 
34
+ def new
35
+ @record = tramway_form model_class.new, namespace: entity.namespace
36
+ end
37
+
38
+ # rubocop:disable Metrics/AbcSize
39
+ def create
40
+ @record = tramway_form model_class.new, namespace: entity.namespace
41
+
42
+ if @record.submit params[model_class.model_name.param_key]
43
+ redirect_to public_send(entity.show_helper_method, @record.id), notice: t('tramway.notices.created')
44
+ else
45
+ render :new
46
+ end
47
+ end
48
+ # rubocop:enable Metrics/AbcSize
49
+
34
50
  private
35
51
 
36
52
  def model_class
@@ -0,0 +1,17 @@
1
+ = tramway_form_for @record, local: true, url: public_send(@entity.create_helper_method), method: :post do |f|
2
+ - if @record.errors.any?
3
+ .alert.alert-danger
4
+ %h4= "#{pluralize(@record.errors.count, 'error')} prohibited this record from being saved:"
5
+ %ul
6
+ - @record.errors.full_messages.each do |message|
7
+ %li= message
8
+
9
+ - @record.class.fields.each do |(attribute, field_type)|
10
+ = f.tramway_field field_type, attribute
11
+
12
+ .flex.flex-row.space-x-2
13
+ = f.submit t('tramway.actions.save')
14
+ = tramway_button text: t('tramway.actions.cancel'),
15
+ path: public_send(@entity.index_helper_method),
16
+ type: :secondary,
17
+ link: true
@@ -7,12 +7,17 @@
7
7
  - content_for :title, page_title
8
8
 
9
9
  .flex.justify-between.items-center
10
- %h1.font-bold.text-4xl.dark:text-white
11
- = content_for(:title)
10
+ = tramway_title do
11
+ = content_for :title
12
12
 
13
13
  - if Tramway.config.pagination[:enabled]
14
14
  = paginate @entities, custom_path_method:
15
15
  .flex.justify-end.mt-2
16
+ - if @entity.page(:create).present?
17
+ = tramway_button text: t('tramway.actions.new'),
18
+ path: Tramway::Engine.routes.url_helpers.public_send(@entity.routes.new),
19
+ type: :will
20
+
16
21
  = decorator.constantize.index_header_content.call(@entities) if decorator.constantize.index_header_content.present?
17
22
 
18
23
  - if index_attributes.empty?
@@ -0,0 +1,5 @@
1
+ = tramway_container do
2
+ = tramway_title text: t('tramway.pages.new.title', model_name: @record.object.model_name.human)
3
+
4
+ .mt-4
5
+ = render 'form'
@@ -27,7 +27,7 @@
27
27
 
28
28
  %div
29
29
  = paginate association[:records],
30
- custom_path_method: "#{@model_class.model_name.to_s.underscore}_path",
30
+ custom_path_method: @entity.index_helper_method,
31
31
  custom_path_arguments: [@record.id]
32
32
 
33
33
  = tramway_table class: 'mt-4' do
@@ -42,5 +42,5 @@
42
42
 
43
43
  .mt-4
44
44
  = paginate association[:records],
45
- custom_path_method: "#{@model_class.model_name.to_s.underscore}_path",
45
+ custom_path_method: @entity.index_helper_method,
46
46
  custom_path_arguments: [@record.id]
@@ -19,8 +19,39 @@
19
19
  / Includes all stylesheet files in app/assets/stylesheets
20
20
  = stylesheet_link_tag "tailwind", "data-turbo-track": "reload"
21
21
 
22
+ :css
23
+ @keyframes fadeout {
24
+ 0% {
25
+ opacity: 1;
26
+ }
27
+
28
+ 80% {
29
+ opacity: 1;
30
+ }
31
+
32
+ 100% {
33
+ opacity: 0;
34
+ }
35
+ }
36
+
37
+ .flash {
38
+ animation: fadeout 2.7s ease-out forwards;
39
+ }
40
+
22
41
  %body.bg-gray-100.dark:bg-gray-900.text-gray-900.dark:text-white
23
42
  = tramway_navbar title: 'Tramway'
24
43
 
44
+ - if flash.any?
45
+ #flash-container.fixed.top-4.right-4.z-50.space-y-2
46
+ - if flash[:notice]
47
+ .flash.opacity-100.bg-green-700.text-white.px-4.py-2.rounded.shadow
48
+ .text-xl.font-bold
49
+ = flash[:notice]
50
+
51
+ - if flash[:alert]
52
+ .flash.opacity-100.bg-yellow-700.text-black.px-4.py-2.rounded.shadow
53
+ .text-xl.font-bold
54
+ = flash[:alert]
55
+
25
56
  .container.mx-auto.p-4.flex.align-center.justify-center
26
57
  = yield
@@ -0,0 +1,7 @@
1
+ en:
2
+ tramway:
3
+ pages:
4
+ new:
5
+ title: "Create %{model_name}"
6
+ notices:
7
+ created: "The record is created"
data/config/routes.rb CHANGED
@@ -14,22 +14,56 @@ Tramway::Engine.routes.draw do
14
14
  resource_name = segments.pop
15
15
 
16
16
  define_resource = proc do
17
- entity.pages.each do |page|
17
+ actions = entity.pages.reduce([]) do |acc, page|
18
18
  case page.action
19
19
  when 'index'
20
- get resource_name.pluralize, to: '/tramway/entities#index', defaults: { entity: }, as: resource_name.pluralize
20
+ acc << :index
21
21
  when 'show'
22
- get "#{resource_name.pluralize}/:id", to: '/tramway/entities#show', defaults: { entity: },
23
- as: resource_name.singularize
22
+ acc << :show
23
+ when 'create'
24
+ acc + %i[create new]
25
+ else
26
+ acc
24
27
  end
25
28
  end
29
+
30
+ resources resource_name.pluralize.to_sym,
31
+ only: actions.map(&:to_sym),
32
+ controller: '/tramway/entities',
33
+ defaults: { entity: }
34
+
35
+ # entity.pages.each do |page|
36
+ # case page.action
37
+ # when 'index'
38
+ # get resource_name.pluralize,
39
+ # to: '/tramway/entities#index',
40
+ # defaults: { entity: },
41
+ # as: resource_name.pluralize
42
+
43
+ # when 'show'
44
+ # get "#{resource_name.pluralize}/:id",
45
+ # to: '/tramway/entities#show',
46
+ # defaults: { entity: },
47
+ # as: resource_name.singularize
48
+
49
+ # when 'create'
50
+ # post resource_name.pluralize,
51
+ # to: '/tramway/entities#create',
52
+ # defaults: { entity: }
53
+
54
+ # get "#{resource_name.pluralize}/new",
55
+ # to: '/tramway/entities#new',
56
+ # defaults: { entity: }
57
+ # end
58
+ # end
26
59
  end
27
60
 
28
61
  if segments.empty?
29
62
  define_resource.call
30
63
  else
31
64
  nest = lambda do |names|
32
- namespace names.first.to_sym do
65
+ namespace_name = names.first.to_sym
66
+ namespace namespace_name do
33
67
  if names.size > 1
34
68
  nest.call(names.drop(1))
35
69
  else
@@ -195,5 +195,22 @@ module.exports = {
195
195
  'h-full',
196
196
  'm-1',
197
197
  'hover:bg-teal-100',
198
+
199
+ // === Flash message styles ===
200
+ 'fixed',
201
+ 'top-6',
202
+ 'right-4',
203
+ 'z-50',
204
+ 'space-y-2',
205
+ 'flash',
206
+ 'opacity-100',
207
+ 'bg-green-700',
208
+ 'text-white',
209
+ 'px-4',
210
+ 'py-2',
211
+ 'rounded',
212
+ 'shadow',
213
+ 'bg-yellow-700',
214
+ 'text-black'
198
215
  ],
199
216
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'tramway/forms/properties'
4
4
  require 'tramway/forms/normalizations'
5
+ require 'tramway/forms/fields'
5
6
  require 'tramway/duck_typing'
6
7
 
7
8
  module Tramway
@@ -9,6 +10,7 @@ module Tramway
9
10
  #
10
11
  class BaseForm
11
12
  include Tramway::Forms::Properties
13
+ include Tramway::Forms::Fields
12
14
  include Tramway::Forms::Normalizations
13
15
  include Tramway::DuckTyping::ActiveRecordCompatibility
14
16
 
@@ -24,6 +26,7 @@ module Tramway
24
26
  def inherited(subclass)
25
27
  __initialize_properties subclass
26
28
  __initialize_normalizations subclass
29
+ __initialize_fields subclass
27
30
 
28
31
  super
29
32
  end
@@ -13,7 +13,7 @@ module Tramway
13
13
  attribute? :namespace, Types::Coercible::String
14
14
 
15
15
  # Route Struct contains implemented in Tramway CRUD and helpful routes for the entity
16
- ACTIONS = %i[index show].freeze
16
+ ACTIONS = %i[index show new create].freeze
17
17
  RouteStruct = Struct.new(*ACTIONS)
18
18
 
19
19
  # HumanName Struct contains human names forms for the entity
@@ -39,6 +39,22 @@ module Tramway
39
39
  pages.find { |page| page.action == name.to_s }
40
40
  end
41
41
 
42
+ def show_helper_method
43
+ build_helper_method(name, route:, namespace:, plural: false)
44
+ end
45
+
46
+ def index_helper_method
47
+ build_helper_method(name, route:, namespace:, plural: true)
48
+ end
49
+
50
+ def new_helper_method
51
+ build_helper_method(name, route:, namespace:, plural: false, action: :new)
52
+ end
53
+
54
+ def create_helper_method
55
+ build_helper_method(name, route:, namespace:, plural: true)
56
+ end
57
+
42
58
  private
43
59
 
44
60
  def pluralized(model_name)
@@ -55,11 +71,18 @@ module Tramway
55
71
 
56
72
  def route_helper_methods
57
73
  ACTIONS.map do |action|
58
- page(action).present? ? send("#{action}_helper_method") : nil
74
+ case action
75
+ when :index, :show
76
+ page(action).present?
77
+ when :new, :create
78
+ page(:create).present?
79
+ end => existing_condition
80
+
81
+ send("#{action}_helper_method") if existing_condition
59
82
  end
60
83
  end
61
84
 
62
- def build_helper_method(base_name, route: nil, namespace: nil, plural: false)
85
+ def build_helper_method(base_name, route: nil, namespace: nil, plural: false, action: nil)
63
86
  if plural
64
87
  base_name.to_s.parameterize.underscore.pluralize
65
88
  else
@@ -67,15 +90,10 @@ module Tramway
67
90
  end => underscored
68
91
 
69
92
  method_name = route.present? ? route.helper_method_by(underscored) : "#{underscored}_path"
70
- namespace.present? ? "#{namespace}_#{method_name}" : method_name
71
- end
72
93
 
73
- def show_helper_method
74
- build_helper_method(name, route:, namespace:, plural: false)
75
- end
94
+ namespaced_method_name = namespace.present? ? "#{namespace}_#{method_name}" : method_name
76
95
 
77
- def index_helper_method
78
- build_helper_method(name, route:, namespace:, plural: true)
96
+ action.present? ? "#{action}_#{namespaced_method_name}" : namespaced_method_name
79
97
  end
80
98
 
81
99
  def route_helper_engine
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Forms
5
+ # Provides field definitions for tramway_form_for
6
+ module Fields
7
+ # Class methods for defining fields
8
+ module ClassMethods
9
+ def fields(**attributes)
10
+ @fields.merge! attributes
11
+ end
12
+
13
+ def __initialize_fields(subclass)
14
+ subclass.instance_variable_set(:@fields, {})
15
+ end
16
+ end
17
+
18
+ def self.included(base)
19
+ base.extend ClassMethods
20
+ end
21
+ end
22
+ end
23
+ end
@@ -38,7 +38,6 @@ module Tramway
38
38
  (__ancestor_properties + @properties).uniq
39
39
  end
40
40
 
41
- # :reek:ManualDispatch { enabled: false }
42
41
  def __ancestor_properties(klass = superclass)
43
42
  superklass = klass.superclass
44
43
 
@@ -47,7 +46,6 @@ module Tramway
47
46
  klass.properties + __ancestor_properties(superklass)
48
47
  end
49
48
 
50
- # :reek:UtilityFunction { enabled: false }
51
49
  def __initialize_properties(subclass)
52
50
  subclass.instance_variable_set(:@properties, [])
53
51
  end
@@ -38,18 +38,12 @@ module Tramway
38
38
  component 'tailwinds/table/cell', &
39
39
  end
40
40
 
41
- def tramway_button(path: nil, text: nil, method: :get, link: false, **options, &)
42
- component 'tailwinds/button',
43
- text:,
44
- path:,
45
- method:,
46
- link:,
47
- color: options.delete(:color),
48
- type: options.delete(:type),
49
- size: options.delete(:size),
50
- options:,
51
- &
41
+ # rubocop:disable Metrics/ParameterLists
42
+ def tramway_button(path: nil, text: nil, method: :get, link: false, form_options: {}, **options, &)
43
+ component 'tailwinds/button', text:, path:, method:, link:, form_options:, color: options.delete(:color),
44
+ type: options.delete(:type), size: options.delete(:size), options:, &
52
45
  end
46
+ # rubocop:enable Metrics/ParameterLists
53
47
 
54
48
  def tramway_back_button
55
49
  component 'tailwinds/back_button'
@@ -69,6 +63,10 @@ module Tramway
69
63
  type:,
70
64
  color:
71
65
  end
66
+
67
+ def tramway_title(text: nil, &)
68
+ component 'tailwinds/title', text:, &
69
+ end
72
70
  end
73
71
  end
74
72
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Utils
5
+ # Provides dynamic field rendering
6
+ module Field
7
+ def tramway_field(field_type, attribute, **, &)
8
+ if field_type.is_a?(Hash)
9
+ name = field_name(field_type[:type])
10
+ value = field_type[:value].call
11
+
12
+ public_send(name, attribute, value:, **, &)
13
+ else
14
+ public_send(field_name(field_type), attribute, **, &)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def field_name(field_type)
21
+ case field_type.to_sym
22
+ when :text_area, :select, :multiselect
23
+ field_type
24
+ else
25
+ "#{field_type}_field"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '1.1.2.2'
4
+ VERSION = '2.0'
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: 1.1.2.2
4
+ version: '2.0'
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-12-04 00:00:00.000000000 Z
12
+ date: 2025-12-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: anyway_config
@@ -209,6 +209,8 @@ files:
209
209
  - app/components/tailwinds/table/row_component.rb
210
210
  - app/components/tailwinds/table_component.html.haml
211
211
  - app/components/tailwinds/table_component.rb
212
+ - app/components/tailwinds/title_component.html.haml
213
+ - app/components/tailwinds/title_component.rb
212
214
  - app/components/tramway/base_component.rb
213
215
  - app/components/tramway/entity_component.html.haml
214
216
  - app/components/tramway/entity_component.rb
@@ -221,10 +223,13 @@ files:
221
223
  - app/views/kaminari/_page.html.haml
222
224
  - app/views/kaminari/_paginator.html.haml
223
225
  - app/views/kaminari/_prev_page.html.haml
226
+ - app/views/tramway/entities/_form.html.haml
224
227
  - app/views/tramway/entities/_list.html.haml
225
228
  - app/views/tramway/entities/index.html.haml
229
+ - app/views/tramway/entities/new.html.haml
226
230
  - app/views/tramway/entities/show.html.haml
227
231
  - app/views/tramway/layouts/application.html.haml
232
+ - config/locales/en.yml
228
233
  - config/routes.rb
229
234
  - config/tailwind.config.js
230
235
  - lib/generators/tramway/install/install_generator.rb
@@ -246,6 +251,7 @@ files:
246
251
  - lib/tramway/duck_typing/active_record_compatibility.rb
247
252
  - lib/tramway/engine.rb
248
253
  - lib/tramway/forms/class_helper.rb
254
+ - lib/tramway/forms/fields.rb
249
255
  - lib/tramway/forms/normalizations.rb
250
256
  - lib/tramway/forms/properties.rb
251
257
  - lib/tramway/helpers/component_helper.rb
@@ -255,6 +261,7 @@ files:
255
261
  - lib/tramway/helpers/routes_helper.rb
256
262
  - lib/tramway/helpers/views_helper.rb
257
263
  - lib/tramway/navbar.rb
264
+ - lib/tramway/utils/field.rb
258
265
  - lib/tramway/utils/render.rb
259
266
  - lib/tramway/version.rb
260
267
  - lib/tramway/views/form_builder.rb