tramway 2.2.3.3 → 2.2.5
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 +12 -4
- data/app/assets/javascripts/tramway/multiselect_controller.js +7 -1
- data/app/components/tailwind_component.rb +3 -3
- data/app/components/tailwinds/form/multiselect/caret_component.html.haml +7 -0
- data/app/components/tailwinds/form/multiselect/caret_component.rb +19 -0
- data/app/components/tailwinds/form/multiselect/dropdown_container_component.rb +26 -0
- data/app/components/tailwinds/form/multiselect/{item_container.html.haml → item_container_component.html.haml} +1 -4
- data/app/components/tailwinds/form/multiselect/item_container_component.rb +24 -0
- data/app/components/tailwinds/form/multiselect/{select_as_input.rb → select_as_input_component.rb} +1 -1
- data/app/components/tailwinds/form/multiselect/selected_item_template_component.html.haml +6 -0
- data/app/components/tailwinds/form/multiselect/selected_item_template_component.rb +26 -0
- data/app/components/tailwinds/form/multiselect_component.html.haml +10 -7
- data/app/components/tailwinds/form/multiselect_component.rb +34 -38
- data/app/components/tailwinds/table/cell_component.rb +0 -17
- data/app/components/tailwinds/table/row_component.rb +18 -0
- data/config/tailwind.config.js +19 -1
- data/docs/AGENTS.md +64 -0
- data/lib/tramway/forms/fields.rb +19 -1
- data/lib/tramway/utils/field.rb +31 -11
- data/lib/tramway/version.rb +1 -1
- metadata +11 -9
- data/app/components/tailwinds/form/multiselect/dropdown_container.rb +0 -17
- data/app/components/tailwinds/form/multiselect/item_container.rb +0 -30
- data/app/components/tailwinds/form/multiselect/selected_item_template.html.haml +0 -6
- data/app/components/tailwinds/form/multiselect/selected_item_template.rb +0 -17
- /data/app/components/tailwinds/form/multiselect/{dropdown_container.html.haml → dropdown_container_component.html.haml} +0 -0
- /data/app/components/tailwinds/form/multiselect/{select_as_input.html.haml → select_as_input_component.html.haml} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd9400e71d346fc1c334cf2f7d2a8a52847c0000957b675cc5bc6c38132a1bb9
|
|
4
|
+
data.tar.gz: 8a36f11fc6dec4e76241f150f8fe0d2d3f8bd187a5ff46f71b6e50cddb660479
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6e6ae7bc9326a651b59fce3c82d88c826f1cff4712248e0c7a0b1e7abd3033b47f91151d3f6d6007a1ba33ae5097455a48aeaf835e328a45edeb329ae8160d6
|
|
7
|
+
data.tar.gz: b546d9583c1c97b7d75797a145de2a522b4e5edd25d09add7cf3b106ac9c6bf89ae541d43667320a4f3adaafef9e7d961bc19f542595258ec5eaed8c698da12d
|
data/README.md
CHANGED
|
@@ -272,13 +272,17 @@ method name and the remaining keys are passed as named arguments.
|
|
|
272
272
|
|
|
273
273
|
```ruby
|
|
274
274
|
class UserForm < Tramway::BaseForm
|
|
275
|
-
properties :email, :about_me
|
|
275
|
+
properties :email, :about_me, :user_type
|
|
276
276
|
|
|
277
277
|
fields email: :email,
|
|
278
278
|
name: :text,
|
|
279
279
|
about_me: {
|
|
280
280
|
type: :text_area,
|
|
281
281
|
rows: 5
|
|
282
|
+
},
|
|
283
|
+
user_type: {
|
|
284
|
+
type: :select,
|
|
285
|
+
collection: ['regular', 'user']
|
|
282
286
|
}
|
|
283
287
|
end
|
|
284
288
|
```
|
|
@@ -652,7 +656,7 @@ end
|
|
|
652
656
|
|
|
653
657
|
### Form inheritance
|
|
654
658
|
|
|
655
|
-
Tramway Form supports inheritance of `properties` and `
|
|
659
|
+
Tramway Form supports inheritance of `properties`, `normalizations`, and `fields`.
|
|
656
660
|
|
|
657
661
|
**Example**
|
|
658
662
|
|
|
@@ -661,6 +665,9 @@ class UserForm < TramwayForm
|
|
|
661
665
|
properties :email, :password
|
|
662
666
|
|
|
663
667
|
normalizes :email, with: ->(value) { value.strip.downcase }
|
|
668
|
+
|
|
669
|
+
fields email: :email,
|
|
670
|
+
password: :password
|
|
664
671
|
end
|
|
665
672
|
|
|
666
673
|
class AdminForm < UserForm
|
|
@@ -668,7 +675,8 @@ class AdminForm < UserForm
|
|
|
668
675
|
end
|
|
669
676
|
|
|
670
677
|
AdminForm.properties # returns [:email, :password, :permissions]
|
|
671
|
-
AdminForm.normalizations # contains the normalization of :email
|
|
678
|
+
AdminForm.normalizations # contains the normalization of :email
|
|
679
|
+
AdminForm.fields # { email: :email, password: :password }
|
|
672
680
|
```
|
|
673
681
|
|
|
674
682
|
### Make flexible and extendable forms
|
|
@@ -1079,7 +1087,7 @@ eagerLoadControllersFrom("controllers", application)
|
|
|
1079
1087
|
application.register('multiselect', Multiselect) // register Multiselect controller class as `multiselect` stimulus controller
|
|
1080
1088
|
```
|
|
1081
1089
|
|
|
1082
|
-
|
|
1090
|
+
In case you need to use Stimulus `change` action with Tramway Multiselect
|
|
1083
1091
|
|
|
1084
1092
|
```erb
|
|
1085
1093
|
<%= tramway_form_for @user do |f| %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class Multiselect extends Controller {
|
|
4
|
-
static targets = ["dropdown", "showSelectedArea", "hiddenInput"]
|
|
4
|
+
static targets = ["dropdown", "showSelectedArea", "hiddenInput", "caretDown", "caretUp"]
|
|
5
5
|
|
|
6
6
|
static values = {
|
|
7
7
|
items: Array,
|
|
@@ -79,6 +79,9 @@ export default class Multiselect extends Controller {
|
|
|
79
79
|
if (this.dropdown()) {
|
|
80
80
|
this.dropdown().addEventListener('click', event => event.stopPropagation());
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
this.caretDownTarget.classList.add('hidden');
|
|
84
|
+
this.caretUpTarget.classList.remove('hidden');
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
dropdown() {
|
|
@@ -107,6 +110,9 @@ export default class Multiselect extends Controller {
|
|
|
107
110
|
alert(`Controller not found: ${controllerName}`); // eslint-disable-line no-undef
|
|
108
111
|
}
|
|
109
112
|
}
|
|
113
|
+
|
|
114
|
+
this.caretDownTarget.classList.remove('hidden');
|
|
115
|
+
this.caretUpTarget.classList.add('hidden');
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
get template() {
|
|
@@ -18,21 +18,21 @@ class TailwindComponent < Tramway::BaseComponent
|
|
|
18
18
|
select_input: 'text-sm px-2 py-1',
|
|
19
19
|
file_button: 'text-sm px-3 py-1',
|
|
20
20
|
submit_button: 'text-sm px-3 py-1',
|
|
21
|
-
multiselect_input: 'text-sm px-2 py-1'
|
|
21
|
+
multiselect_input: 'text-sm px-2 py-1 h-10'
|
|
22
22
|
},
|
|
23
23
|
medium: {
|
|
24
24
|
text_input: 'text-base px-3 py-2',
|
|
25
25
|
select_input: 'text-base px-3 py-2',
|
|
26
26
|
file_button: 'text-base px-4 py-2',
|
|
27
27
|
submit_button: 'text-base px-4 py-2',
|
|
28
|
-
multiselect_input: 'text-base px-
|
|
28
|
+
multiselect_input: 'text-base px-2 py-1 h-12'
|
|
29
29
|
},
|
|
30
30
|
large: {
|
|
31
31
|
text_input: 'text-xl px-4 py-3',
|
|
32
32
|
select_input: 'text-xl px-4 py-3',
|
|
33
33
|
file_button: 'text-xl px-5 py-3',
|
|
34
34
|
submit_button: 'text-xl px-5 py-3',
|
|
35
|
-
multiselect_input: 'text-xl px-
|
|
35
|
+
multiselect_input: 'text-xl px-3 py-2 h-15'
|
|
36
36
|
}
|
|
37
37
|
}.freeze
|
|
38
38
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
- if direction == :down
|
|
2
|
+
%svg{ xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "stroke-width": "1.5", stroke: "currentColor", class: SIZE_CLASSES[size] }
|
|
3
|
+
%path{ "stroke-linecap": "round", "stroke-linejoin": "round", d: "m19.5 8.25-7.5 7.5-7.5-7.5" }
|
|
4
|
+
|
|
5
|
+
- if direction == :up
|
|
6
|
+
%svg{ xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "stroke-width": "1.5", stroke: "currentColor", class: SIZE_CLASSES[size] }
|
|
7
|
+
%path{ "stroke-linecap": "round", "stroke-linejoin": "round", d: "m4.5 15.75 7.5-7.5 7.5 7.5" }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailwinds
|
|
4
|
+
module Form
|
|
5
|
+
module Multiselect
|
|
6
|
+
# Caret icon component
|
|
7
|
+
class CaretComponent < Tramway::BaseComponent
|
|
8
|
+
option :direction
|
|
9
|
+
option :size
|
|
10
|
+
|
|
11
|
+
SIZE_CLASSES = {
|
|
12
|
+
small: 'w-3 h-3',
|
|
13
|
+
medium: 'w-4 h-4',
|
|
14
|
+
large: 'w-6 h-6'
|
|
15
|
+
}.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailwinds
|
|
4
|
+
module Form
|
|
5
|
+
module Multiselect
|
|
6
|
+
# Container for dropdown component
|
|
7
|
+
class DropdownContainerComponent < Tramway::BaseComponent
|
|
8
|
+
option :size
|
|
9
|
+
|
|
10
|
+
SIZE_CLASSES = {
|
|
11
|
+
small: 'text-sm',
|
|
12
|
+
medium: 'text-base',
|
|
13
|
+
large: 'text-lg'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def dropdown_classes
|
|
17
|
+
theme_classes(
|
|
18
|
+
classic: 'absolute border-b border-l border-r border-gray-700 w-full z-40 lef-0 rounded-b-xl' \
|
|
19
|
+
'max-h-select overflow-y-auto bg-gray-900 shadow-md ring-1 ring-gray-700 text-white ' \
|
|
20
|
+
"#{SIZE_CLASSES[size]}"
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailwinds
|
|
4
|
+
module Form
|
|
5
|
+
module Multiselect
|
|
6
|
+
# Container for item in dropdown component
|
|
7
|
+
class ItemContainerComponent < Tramway::BaseComponent
|
|
8
|
+
option :size
|
|
9
|
+
|
|
10
|
+
SIZE_CLASSES = {
|
|
11
|
+
small: 'p-1',
|
|
12
|
+
medium: 'p-2',
|
|
13
|
+
large: 'p-3'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def item_classes
|
|
17
|
+
theme_classes(
|
|
18
|
+
classic: "cursor-pointer hover:bg-gray-800 shadow-inner option #{SIZE_CLASSES[size]}"
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailwinds
|
|
4
|
+
module Form
|
|
5
|
+
module Multiselect
|
|
6
|
+
# Tailwind-styled multi-select field
|
|
7
|
+
class SelectedItemTemplateComponent < Tramway::BaseComponent
|
|
8
|
+
option :size
|
|
9
|
+
|
|
10
|
+
SIZE_CLASSES = {
|
|
11
|
+
small: 'text-sm',
|
|
12
|
+
medium: 'text-base',
|
|
13
|
+
large: 'text-lg'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def selected_item_classes
|
|
17
|
+
theme_classes(
|
|
18
|
+
classic: 'flex justify-center items-center font-medium py-1 px-2 rounded-xl border ' \
|
|
19
|
+
'text-white border-gray-700 shadow-md hover:bg-gray-800 cursor-pointer space-x-1 ' \
|
|
20
|
+
'selected-option ' + SIZE_CLASSES[size].to_s
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
.mb-4
|
|
1
|
+
.mb-4.relative
|
|
2
2
|
- if @label
|
|
3
3
|
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
|
-
%div{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
%div{ role: :combobox, data: multiselect_hash, id: "#{@for}_multiselect" }
|
|
6
|
+
- classes = "#{size_class(:multiselect_input)} #{select_base_classes}"
|
|
7
|
+
.flex.flex-end.justify-between{ data: dropdown_data, **dropdown_options }
|
|
8
|
+
.flex.flex-row.flex-nowrap.overflow-x-auto.space-x-1{ data: { "multiselect-target" => "showSelectedArea" } }
|
|
9
|
+
.flex.flex-col.justify-center
|
|
10
|
+
.caret-down{ data: { "multiselect-target" => "caretDown" } }
|
|
11
|
+
= component 'tailwinds/form/multiselect/caret', size: size, direction: :down
|
|
12
|
+
.caret-up.hidden{ data: { "multiselect-target" => "caretUp" } }
|
|
13
|
+
= component 'tailwinds/form/multiselect/caret', size: size, direction: :up
|
|
@@ -12,12 +12,23 @@ module Tailwinds
|
|
|
12
12
|
end.to_json
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# rubocop:disable Metrics/MethodLength
|
|
15
16
|
def multiselect_hash
|
|
16
17
|
{
|
|
17
|
-
controller:,
|
|
18
|
-
|
|
18
|
+
controller:,
|
|
19
|
+
selected_item_template:,
|
|
20
|
+
multiselect_selected_items_value:,
|
|
21
|
+
dropdown_container:,
|
|
22
|
+
item_container:,
|
|
23
|
+
items:,
|
|
24
|
+
action:,
|
|
25
|
+
select_as_input:,
|
|
26
|
+
placeholder:,
|
|
27
|
+
value:,
|
|
28
|
+
on_change:
|
|
19
29
|
}.transform_keys { |key| key.to_s.gsub('_', '-') }
|
|
20
30
|
end
|
|
31
|
+
# rubocop:enable Metrics/MethodLength
|
|
21
32
|
|
|
22
33
|
def controller
|
|
23
34
|
controllers = [:multiselect]
|
|
@@ -26,22 +37,20 @@ module Tailwinds
|
|
|
26
37
|
controllers.join(' ')
|
|
27
38
|
end
|
|
28
39
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
def dropdown_data
|
|
41
|
+
(options[:data] || {}).merge(
|
|
42
|
+
'multiselect-target' => 'dropdown',
|
|
43
|
+
'dropdown-container' => dropdown_container,
|
|
44
|
+
'item-container' => item_container
|
|
32
45
|
)
|
|
33
46
|
end
|
|
34
47
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
classic: 'p-1 flex border rounded-xl border-gray-700 bg-gray-900 shadow-inner'
|
|
38
|
-
)
|
|
48
|
+
def dropdown_options
|
|
49
|
+
options.except(:data).merge(class: input_classes)
|
|
39
50
|
end
|
|
40
51
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
classic: 'w-8 py-1 pl-2 pr-1 border-l flex items-center text-gray-500 border-gray-700'
|
|
44
|
-
)
|
|
52
|
+
def input_classes
|
|
53
|
+
"#{size_class(:multiselect_input)} #{select_base_classes}"
|
|
45
54
|
end
|
|
46
55
|
|
|
47
56
|
private
|
|
@@ -63,13 +72,12 @@ module Tailwinds
|
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
def select_as_input
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
75
|
+
component(
|
|
76
|
+
'tailwinds/form/multiselect/select_as_input',
|
|
77
|
+
options:,
|
|
78
|
+
attribute:,
|
|
79
|
+
input:,
|
|
80
|
+
size_class: size_class(:multiselect_input)
|
|
73
81
|
)
|
|
74
82
|
end
|
|
75
83
|
|
|
@@ -87,29 +95,17 @@ module Tailwinds
|
|
|
87
95
|
options.dig(:data, :action)
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
def
|
|
91
|
-
component
|
|
92
|
-
|
|
93
|
-
if method_name.to_s.include?('_') && Object.const_defined?(component)
|
|
94
|
-
render(component.constantize.new(*, &))
|
|
95
|
-
else
|
|
96
|
-
super
|
|
97
|
-
end
|
|
98
|
+
def selected_item_template
|
|
99
|
+
component('tailwinds/form/multiselect/selected_item_template', size:)
|
|
98
100
|
end
|
|
99
101
|
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
true
|
|
103
|
-
else
|
|
104
|
-
super
|
|
105
|
-
end
|
|
102
|
+
def dropdown_container
|
|
103
|
+
component('tailwinds/form/multiselect/dropdown_container', size:)
|
|
106
104
|
end
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"Tailwinds::Form::Multiselect::#{method_name.to_s.camelize}"
|
|
106
|
+
def item_container
|
|
107
|
+
component('tailwinds/form/multiselect/item_container', size:)
|
|
111
108
|
end
|
|
112
|
-
# :reek:UtilityFunction { enabled: true }
|
|
113
109
|
end
|
|
114
110
|
end
|
|
115
111
|
end
|
|
@@ -9,23 +9,6 @@ module Tailwinds
|
|
|
9
9
|
classic: 'div-table-cell md:block first:block hidden px-6 py-4 font-medium text-gray-100 text-base'
|
|
10
10
|
)
|
|
11
11
|
end
|
|
12
|
-
|
|
13
|
-
def around_render
|
|
14
|
-
ensure_view_context_accessor
|
|
15
|
-
previous_flag = view_context.tramway_inside_cell
|
|
16
|
-
view_context.tramway_inside_cell = true
|
|
17
|
-
yield
|
|
18
|
-
ensure
|
|
19
|
-
view_context.tramway_inside_cell = previous_flag
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def ensure_view_context_accessor
|
|
25
|
-
return if view_context.respond_to?(:tramway_inside_cell=)
|
|
26
|
-
|
|
27
|
-
view_context.singleton_class.attr_accessor :tramway_inside_cell
|
|
28
|
-
end
|
|
29
12
|
end
|
|
30
13
|
end
|
|
31
14
|
end
|
|
@@ -44,6 +44,24 @@ module Tailwinds
|
|
|
44
44
|
classic: 'div-table-cell px-6 py-4 font-medium text-gray-100 text-xs sm:text-base'
|
|
45
45
|
)
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
def around_render
|
|
49
|
+
ensure_view_context_accessor
|
|
50
|
+
previous_flag = view_context.tramway_inside_cell
|
|
51
|
+
view_context.tramway_inside_cell = href.present?
|
|
52
|
+
|
|
53
|
+
yield
|
|
54
|
+
ensure
|
|
55
|
+
view_context.tramway_inside_cell = previous_flag
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def ensure_view_context_accessor
|
|
61
|
+
return if view_context.respond_to?(:tramway_inside_cell=)
|
|
62
|
+
|
|
63
|
+
view_context.singleton_class.attr_accessor :tramway_inside_cell
|
|
64
|
+
end
|
|
47
65
|
end
|
|
48
66
|
end
|
|
49
67
|
end
|
data/config/tailwind.config.js
CHANGED
|
@@ -247,7 +247,6 @@ module.exports = {
|
|
|
247
247
|
'absolute',
|
|
248
248
|
'relative',
|
|
249
249
|
'shadow',
|
|
250
|
-
'top-11',
|
|
251
250
|
'z-40',
|
|
252
251
|
'max-h-select',
|
|
253
252
|
'overflow-y-auto',
|
|
@@ -269,6 +268,25 @@ module.exports = {
|
|
|
269
268
|
'outline-none',
|
|
270
269
|
'h-full',
|
|
271
270
|
'm-1',
|
|
271
|
+
'p-1',
|
|
272
|
+
'p-2',
|
|
273
|
+
'p-3',
|
|
274
|
+
'rounded-b-xl',
|
|
275
|
+
'hover:bg-gray-800',
|
|
276
|
+
'border-gray-700',
|
|
277
|
+
'h-15',
|
|
278
|
+
'h-12',
|
|
279
|
+
'h-10',
|
|
280
|
+
'w-2',
|
|
281
|
+
'h-2',
|
|
282
|
+
'w-4',
|
|
283
|
+
'h-4',
|
|
284
|
+
'w-6',
|
|
285
|
+
'h-6',
|
|
286
|
+
'px-2',
|
|
287
|
+
'py-1',
|
|
288
|
+
'flex-nowrap',
|
|
289
|
+
'overflow-x-auto',
|
|
272
290
|
|
|
273
291
|
// === Flash message styles ===
|
|
274
292
|
'fixed',
|
data/docs/AGENTS.md
CHANGED
|
@@ -358,6 +358,70 @@ In case you implementing API, use `api` namespaces for forms and decorators.
|
|
|
358
358
|
### Rule 25
|
|
359
359
|
DO NOT use `#{model_name}_params` method with `permit` method inside controllers. When you use `tramway_form`, it's unnecessary.
|
|
360
360
|
|
|
361
|
+
### Rule 26
|
|
362
|
+
DO NOT create new private methods in the controller for business logic stuff. Use service objects instead.
|
|
363
|
+
Create `app/services/base_service.rb` if it does not exist.
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
class BaseService
|
|
367
|
+
extend Dry::Initializer[undefined: false]
|
|
368
|
+
include Dry::Monads[:do, :result]
|
|
369
|
+
|
|
370
|
+
def self.call(...)
|
|
371
|
+
new(...).call
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
And instead of this in a controller
|
|
377
|
+
|
|
378
|
+
```ruby
|
|
379
|
+
def create
|
|
380
|
+
user = tramway_form User.new
|
|
381
|
+
|
|
382
|
+
if user.submit params[:user]
|
|
383
|
+
notify_admin user
|
|
384
|
+
|
|
385
|
+
# other stuff
|
|
386
|
+
else
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
private
|
|
391
|
+
|
|
392
|
+
def notify_admin(user)
|
|
393
|
+
# stuff
|
|
394
|
+
end
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Make this
|
|
398
|
+
|
|
399
|
+
*app/services/notify_admin.rb*
|
|
400
|
+
```ruby
|
|
401
|
+
class NotifyAdmin < BaseService
|
|
402
|
+
option :user
|
|
403
|
+
|
|
404
|
+
def call
|
|
405
|
+
# stuff
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
and in a controller
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
def create
|
|
414
|
+
user = tramway_form User.new
|
|
415
|
+
|
|
416
|
+
if user.submit params[:user]
|
|
417
|
+
NotifyAdmin.call user:
|
|
418
|
+
|
|
419
|
+
# other stuff
|
|
420
|
+
else
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
361
425
|
## Controller Patterns
|
|
362
426
|
|
|
363
427
|
- Keep actions short and explicit with guard clauses.
|
data/lib/tramway/forms/fields.rb
CHANGED
|
@@ -7,7 +7,25 @@ module Tramway
|
|
|
7
7
|
# Class methods for defining fields
|
|
8
8
|
module ClassMethods
|
|
9
9
|
def fields(**attributes)
|
|
10
|
-
|
|
10
|
+
attributes.any? ? __set_fields(attributes) : __fields
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def __set_fields(attributes)
|
|
14
|
+
attributes.each do |(attribute, field_data)|
|
|
15
|
+
@fields.merge! attribute => field_data
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def __fields
|
|
20
|
+
@fields.merge(__ancestor_fields)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def __ancestor_fields(klass = superclass)
|
|
24
|
+
superklass = klass.superclass
|
|
25
|
+
|
|
26
|
+
return {} unless superklass.respond_to?(:fields)
|
|
27
|
+
|
|
28
|
+
klass.fields.merge(__ancestor_fields(superklass))
|
|
11
29
|
end
|
|
12
30
|
|
|
13
31
|
def __initialize_fields(subclass)
|
data/lib/tramway/utils/field.rb
CHANGED
|
@@ -4,27 +4,47 @@ module Tramway
|
|
|
4
4
|
module Utils
|
|
5
5
|
# Provides dynamic field rendering
|
|
6
6
|
module Field
|
|
7
|
-
def tramway_field(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
def tramway_field(field_data, attribute, **options, &)
|
|
8
|
+
input_type = field_type(field_data)
|
|
9
|
+
input_name = field_name input_type
|
|
10
|
+
input_options = field_options(field_data).merge(options)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
case input_type.to_sym
|
|
13
|
+
when :select, :multiselect
|
|
14
|
+
collection = input_options.delete(:collection)
|
|
13
15
|
|
|
14
|
-
public_send(
|
|
16
|
+
public_send(input_name, attribute, collection, **input_options, &)
|
|
15
17
|
else
|
|
16
|
-
public_send(
|
|
18
|
+
public_send(input_name, attribute, **input_options, &)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
private
|
|
21
23
|
|
|
22
|
-
def field_name(
|
|
23
|
-
case
|
|
24
|
+
def field_name(field_data)
|
|
25
|
+
case field_data.to_sym
|
|
24
26
|
when :text_area, :select, :multiselect
|
|
25
|
-
|
|
27
|
+
field_data
|
|
26
28
|
else
|
|
27
|
-
"#{
|
|
29
|
+
"#{field_data}_field"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def field_type(field_data)
|
|
34
|
+
if field_data.is_a?(Hash)
|
|
35
|
+
field_data[:type]
|
|
36
|
+
else
|
|
37
|
+
field_data
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def field_options(field_data)
|
|
42
|
+
if field_data.is_a?(Hash)
|
|
43
|
+
value = field_data[:value]&.call
|
|
44
|
+
|
|
45
|
+
field_data.merge(value:).except(:type)
|
|
46
|
+
else
|
|
47
|
+
{}
|
|
28
48
|
end
|
|
29
49
|
end
|
|
30
50
|
end
|
data/lib/tramway/version.rb
CHANGED
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: 2.2.
|
|
4
|
+
version: 2.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kalashnikovisme
|
|
@@ -164,14 +164,16 @@ files:
|
|
|
164
164
|
- app/components/tailwinds/form/file_field_component.rb
|
|
165
165
|
- app/components/tailwinds/form/label_component.html.haml
|
|
166
166
|
- app/components/tailwinds/form/label_component.rb
|
|
167
|
-
- app/components/tailwinds/form/multiselect/
|
|
168
|
-
- app/components/tailwinds/form/multiselect/
|
|
169
|
-
- app/components/tailwinds/form/multiselect/
|
|
170
|
-
- app/components/tailwinds/form/multiselect/
|
|
171
|
-
- app/components/tailwinds/form/multiselect/
|
|
172
|
-
- app/components/tailwinds/form/multiselect/
|
|
173
|
-
- app/components/tailwinds/form/multiselect/
|
|
174
|
-
- app/components/tailwinds/form/multiselect/
|
|
167
|
+
- app/components/tailwinds/form/multiselect/caret_component.html.haml
|
|
168
|
+
- app/components/tailwinds/form/multiselect/caret_component.rb
|
|
169
|
+
- app/components/tailwinds/form/multiselect/dropdown_container_component.html.haml
|
|
170
|
+
- app/components/tailwinds/form/multiselect/dropdown_container_component.rb
|
|
171
|
+
- app/components/tailwinds/form/multiselect/item_container_component.html.haml
|
|
172
|
+
- app/components/tailwinds/form/multiselect/item_container_component.rb
|
|
173
|
+
- app/components/tailwinds/form/multiselect/select_as_input_component.html.haml
|
|
174
|
+
- app/components/tailwinds/form/multiselect/select_as_input_component.rb
|
|
175
|
+
- app/components/tailwinds/form/multiselect/selected_item_template_component.html.haml
|
|
176
|
+
- app/components/tailwinds/form/multiselect/selected_item_template_component.rb
|
|
175
177
|
- app/components/tailwinds/form/multiselect_component.html.haml
|
|
176
178
|
- app/components/tailwinds/form/multiselect_component.rb
|
|
177
179
|
- app/components/tailwinds/form/number_field_component.html.haml
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailwinds
|
|
4
|
-
module Form
|
|
5
|
-
module Multiselect
|
|
6
|
-
# Container for dropdown component
|
|
7
|
-
class DropdownContainer < Tramway::BaseComponent
|
|
8
|
-
def dropdown_classes
|
|
9
|
-
theme_classes(
|
|
10
|
-
classic: 'absolute shadow top-11 z-40 w-full lef-0 rounded-xl max-h-select overflow-y-auto ' \
|
|
11
|
-
'bg-gray-900 shadow-md ring-1 ring-gray-700'
|
|
12
|
-
)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailwinds
|
|
4
|
-
module Form
|
|
5
|
-
module Multiselect
|
|
6
|
-
# Container for item in dropdown component
|
|
7
|
-
class ItemContainer < Tramway::BaseComponent
|
|
8
|
-
def item_classes
|
|
9
|
-
theme_classes(
|
|
10
|
-
classic: 'cursor-pointer w-full rounded-xl border-b border-gray-700 bg-gray-900 ' \
|
|
11
|
-
'hover:bg-gray-800 shadow-inner'
|
|
12
|
-
)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def item_inner_classes
|
|
16
|
-
theme_classes(
|
|
17
|
-
classic: 'flex w-full items-center p-2 pl-2 border-transparent border-l-2 relative ' \
|
|
18
|
-
'hover:border-gray-600'
|
|
19
|
-
)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def item_text_classes
|
|
23
|
-
theme_classes(
|
|
24
|
-
classic: 'w-full items-center flex text-gray-100'
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailwinds
|
|
4
|
-
module Form
|
|
5
|
-
module Multiselect
|
|
6
|
-
# Tailwind-styled multi-select field
|
|
7
|
-
class SelectedItemTemplate < Tramway::BaseComponent
|
|
8
|
-
def selected_item_classes
|
|
9
|
-
theme_classes(
|
|
10
|
-
classic: 'flex justify-center items-center m-1 font-medium py-1 px-2 rounded-full border ' \
|
|
11
|
-
'bg-teal-900 text-teal-100 border-teal-700 shadow-md'
|
|
12
|
-
)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
File without changes
|
|
File without changes
|