tramway 3.0.1 → 3.0.2

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: 56f897f311d05eaa3d077f0f02a2b28d116bd2bbdfb7f460b05ab3d481b5b57e
4
- data.tar.gz: 4dec440f2451c89ff6b1bd10c93d79ff9a12a99c528129bb19b267bd7f59b95b
3
+ metadata.gz: def2792e8c3e1ebae5e4d2fa04b7e5edf92b72e4aed0f892cf8152e203c8daf2
4
+ data.tar.gz: 77eace6936af7c9597f49634ea5da28f699b4011f4ccac811259e659732ca192
5
5
  SHA512:
6
- metadata.gz: 732effab54397590ae4320802c803c70fe648765ff200ce0e994e396c13eabed2501361c645a7e2a55019a81cdba4987e26615c7c71e5aa05ab1add573188d06
7
- data.tar.gz: 77a23247e58a804ff62e2c4a1071db86aae1d746832268d39da9e70fcab5e276264b16d60125da72be38ff40e4f2a01c182cd453b56c66eb02766fe649f65f7c
6
+ metadata.gz: d6f7a8ed6f51a4658625b8df8f1db6ec2eb7619284eafc6acf0b46d67f0fb9eaf3bfe95e818d7226f52a93a6b81eb271e64335acf76640501132b890e000938d
7
+ data.tar.gz: 197a0227f09dbccacf37e82d2e5d15a585928b0ec60863c1ce43c20e012b568037e0880f9ffda49a21846c66dcec72f6b1931ff0790509d9c01b6b8da71727d8
data/README.md CHANGED
@@ -1227,6 +1227,17 @@ In case you need to use Stimulus `change` action with Tramway Select
1227
1227
  <% end %>
1228
1228
  ```
1229
1229
 
1230
+ Remote form example:
1231
+
1232
+ ```erb
1233
+ <%= tramway_form_for @user, remote: true do |f| %>
1234
+ <%= f.text_field :name %>
1235
+ <%= f.email_field :email %>
1236
+ <% end %>
1237
+ ```
1238
+
1239
+ With `remote: true`, Tramway submits the form on each input `change` via inline JavaScript; no additional controller setup is required.
1240
+
1230
1241
  ### Tailwind-styled pagination for Kaminari
1231
1242
 
1232
1243
  Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari).
@@ -12,13 +12,19 @@ module Tramway
12
12
 
13
13
  def initialize(object_name, object, template, options)
14
14
  @horizontal = options[:horizontal] || false
15
+ @remote = options[:remote_submit] || false
15
16
 
16
17
  options.merge!(class: [options[:class], 'flex flex-row items-center gap-2'].compact.join(' ')) if @horizontal
17
18
 
18
- super
19
+ @form_object_class = options[:form_object_class]
20
+
21
+ if form_object(object)
22
+ super(object_name, form_object(object), template, options)
23
+ else
24
+ super
25
+ end
19
26
 
20
27
  @form_size = options[:size] || options['size'] || :medium
21
- @form_object_class = options[:form_object_class]
22
28
  end
23
29
 
24
30
  def common_field(component_name, input_method, attribute, **options, &)
@@ -137,20 +143,55 @@ module Tramway
137
143
  unbound_method.bind(self)
138
144
  end
139
145
 
140
- def form_object
141
- @form_object_class&.new object
146
+ def form_object(obj = nil)
147
+ return obj if obj.is_a?(Tramway::BaseForm)
148
+ return object if object.is_a?(Tramway::BaseForm)
149
+
150
+ @form_object_class&.new(obj || object)
142
151
  end
143
152
 
144
153
  def get_value(attribute, options)
145
- options[:value] || form_object&.public_send(attribute).presence || object.presence&.public_send(attribute)
154
+ return options[:value] if options.key?(:value)
155
+
156
+ form_obj = form_object
157
+ form_value = form_object_value(form_obj, attribute)
158
+ return form_value unless form_value.nil?
159
+
160
+ ensure_object_responds!(attribute, form_obj)
161
+ object_value(attribute)
162
+ end
163
+
164
+ def form_object_value(form_obj, attribute)
165
+ return if form_obj.blank?
166
+
167
+ form_obj.public_send(attribute)
168
+ end
169
+
170
+ def ensure_object_responds!(attribute, form_obj)
171
+ return unless object.present? && !object.respond_to?(attribute)
172
+
173
+ form_object_part = form_obj.present? ? "#{form_obj.class} or " : ''
174
+ message = "Neither form object nor object respond to #{attribute}. " \
175
+ "You should define #{attribute} method in #{form_object_part}#{object.class}"
176
+
177
+ raise ArgumentError, message
178
+ end
179
+
180
+ def object_value(attribute)
181
+ return if object.blank?
182
+
183
+ object.public_send(attribute)
146
184
  end
147
185
 
148
186
  def default_options(attribute, options)
187
+ options.merge!(horizontal: true) if @horizontal
188
+ options.merge!(onchange: 'this.form.requestSubmit()') if @remote
189
+
149
190
  {
150
191
  attribute:,
151
192
  label: label_build(attribute, options),
152
- for: for_id(attribute),
153
- options: options.merge(horizontal: @horizontal),
193
+ for: options[:id].presence || for_id(attribute),
194
+ options: options,
154
195
  size: form_size
155
196
  }
156
197
  end
@@ -1,7 +1,7 @@
1
- .flex.items-start.gap-2{ class: default_container_classes }
1
+ .flex.items-center.gap-2.cursor-pointer{ class: default_container_classes }
2
2
  - classes = "#{size_class(:checkbox_input)} #{checkbox_base_classes}"
3
3
  = @input.call @attribute, **@options.merge(class: classes)
4
4
  - if @label
5
5
  %div
6
- = component('tramway/form/label', for: @for) do
6
+ = component('tramway/form/label', for: @for, options: { class: label_classes }) do
7
7
  = @label
@@ -4,6 +4,20 @@ module Tramway
4
4
  module Form
5
5
  # Tailwind-styled checkbox field
6
6
  class CheckboxComponent < TailwindComponent
7
+ def label_classes
8
+ default_classes = 'cursor-pointer mb-0'
9
+
10
+ case size
11
+ when :small
12
+ default_classes += ' text-sm'
13
+ when :medium
14
+ default_classes += ' text-base'
15
+ when :large
16
+ default_classes += ' text-lg'
17
+ end
18
+
19
+ default_classes
20
+ end
7
21
  end
8
22
  end
9
23
  end
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
6
  = @input.call @attribute, **@options.merge(class: classes), value: @value
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
6
  = @input.call @attribute, **@options.merge(class: classes), value: @value
@@ -1,2 +1,2 @@
1
- %label{ for: @for, class: form_label_classes }
1
+ %label{ for: @for, class: "#{form_label_classes} #{options[:class]}" }
2
2
  = content
@@ -5,10 +5,11 @@ module Tramway
5
5
  # Form label for all tailwind-styled forms
6
6
  class LabelComponent < Tramway::BaseComponent
7
7
  option :for
8
+ option :options, optional: true, default: -> { {} }
8
9
 
9
10
  def form_label_classes
10
11
  theme_classes(
11
- classic: 'block text-sm font-semibold mb-2 text-white'
12
+ classic: 'block font-semibold text-white'
12
13
  )
13
14
  end
14
15
  end
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
6
  = @input.call @attribute, **@options.merge(class: classes), value: @value
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:select_input)} #{select_base_classes}"
6
6
  = @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: classes))
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
6
  = @input.call @attribute, **@options.merge(class: classes), value: @value
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
6
  = @input.call @attribute, **@options.merge(class: classes), value: @value
@@ -1,6 +1,6 @@
1
1
  %div{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
6
  = @input.call @attribute, **@options.merge(class: classes), value: @value
@@ -1,6 +1,6 @@
1
1
  .relative{ class: default_container_classes }
2
2
  - if @label
3
- = component('tramway/form/label', for: @for) do
3
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  %div{ role: :combobox, data: tramway_select_hash, id: "#{@for}_tramway_select", class: tramway_select_classes }
6
6
  - classes = "#{size_class(:tramway_select_input)} #{select_base_classes}"
@@ -333,6 +333,10 @@ module.exports = {
333
333
  'bg-red-100',
334
334
  'text-red-800',
335
335
  'space-x-2',
336
+ 'h-5',
337
+ 'w-5',
338
+ 'rounded-full',
339
+ 'mb-0',
336
340
 
337
341
  // === Tramway select dropdown positioning ===
338
342
  'absolute',
data/docs/AGENTS.md CHANGED
@@ -173,6 +173,10 @@ Available `tramway_form_for` helpers:
173
173
  - `tramway_select`
174
174
  - `submit`
175
175
 
176
+ ### Rule 7.1
177
+ Use `tramway_form_for(remote: true)` only when the form must submit asynchronously and update part of the current page (for example: modal forms, inline edits, or list updates without full page reload).
178
+ For standard create/update flows that redirect and show regular flash messages, keep it synchronous (do not set `remote: true`).
179
+
176
180
  ### Rule 8
177
181
  Inherit all components from Tramway::BaseComponent
178
182
 
@@ -532,7 +536,15 @@ Always `tramway_decorate` and `tramway_form` for creating these types of objects
532
536
  In Tramway Decorators, use `delegate_attributes` method instead of `delegate :something, to: :object`
533
537
 
534
538
  ### Rule 33
535
- In case you want to use container on the page, use `tramway_container` helper instead of creating a component for that or using a plain div with Tailwind classes.
539
+ In case you want to use container on the page, use `tramway_container` helper instead of creating a component for that or using a plain div with Tailwind classes. In case you need to use container in layout view, use `tramway_main_container` for this. Here is example of using `tramway_main_container` inside application layout.
540
+
541
+ ```
542
+ = tramway_main_container do
543
+ - if flash.any?
544
+ = tramway_flash text: flash[:notice].presence || flash[:alert],
545
+ type: flash[:notice].present? ? :will : :rage,
546
+ id: 'flash-container'
547
+ ```
536
548
 
537
549
  ## Controller Patterns
538
550
 
@@ -11,12 +11,12 @@ module Tramway
11
11
  def tramway_form_for(object, *, size: :medium, **options, &)
12
12
  form_object_class = object.is_a?(Tramway::BaseForm) ? object.class : nil
13
13
 
14
- form_for(
15
- object,
16
- *,
17
- **options.merge(builder: Tramway::Form::Builder, size: normalize_form_size(size), form_object_class:),
18
- &
19
- )
14
+ form_for(object, *, **options.merge(
15
+ builder: Tramway::Form::Builder,
16
+ size: normalize_form_size(size),
17
+ form_object_class:,
18
+ remote_submit: options[:remote] || false
19
+ ), &)
20
20
  end
21
21
 
22
22
  def tramway_table(**options, &)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '3.0.1'
4
+ VERSION = '3.0.2'
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: 3.0.1
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - kalashnikovisme