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 +4 -4
- data/README.md +11 -0
- data/app/components/tramway/form/builder.rb +48 -7
- data/app/components/tramway/form/checkbox_component.html.haml +2 -2
- data/app/components/tramway/form/checkbox_component.rb +14 -0
- data/app/components/tramway/form/date_field_component.html.haml +1 -1
- data/app/components/tramway/form/datetime_field_component.html.haml +1 -1
- data/app/components/tramway/form/label_component.html.haml +1 -1
- data/app/components/tramway/form/label_component.rb +2 -1
- data/app/components/tramway/form/number_field_component.html.haml +1 -1
- data/app/components/tramway/form/select_component.html.haml +1 -1
- data/app/components/tramway/form/text_area_component.html.haml +1 -1
- data/app/components/tramway/form/text_field_component.html.haml +1 -1
- data/app/components/tramway/form/time_field_component.html.haml +1 -1
- data/app/components/tramway/form/tramway_select_component.html.haml +1 -1
- data/config/tailwind.config.js +4 -0
- data/docs/AGENTS.md +13 -1
- data/lib/tramway/helpers/views_helper.rb +6 -6
- data/lib/tramway/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: def2792e8c3e1ebae5e4d2fa04b7e5edf92b72e4aed0f892cf8152e203c8daf2
|
|
4
|
+
data.tar.gz: 77eace6936af7c9597f49634ea5da28f699b4011f4ccac811259e659732ca192
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
|
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-
|
|
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
|
|
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}"
|
data/config/tailwind.config.js
CHANGED
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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, &)
|
data/lib/tramway/version.rb
CHANGED