tramway 0.5.0.1 → 0.5.1
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 +7 -0
- data/app/assets/javascripts/tramway/multiselect_controller.js +27 -4
- data/app/components/tailwinds/form/builder.rb +1 -1
- data/app/components/tailwinds/form/multiselect_component.rb +19 -2
- data/app/components/tailwinds/navbar_component.html.haml +4 -4
- data/app/components/tailwinds/pagination/first_page_component.html.haml +9 -2
- data/app/components/tailwinds/pagination/gap_component.html.haml +1 -1
- data/app/components/tailwinds/pagination/last_page_component.html.haml +9 -2
- data/app/components/tailwinds/pagination/next_page_component.html.haml +11 -2
- data/app/components/tailwinds/pagination/page_component.html.haml +5 -1
- data/app/components/tailwinds/pagination/prev_page_component.html.haml +11 -2
- data/app/components/tailwinds/table/header_component.html.haml +1 -1
- data/app/components/tailwinds/table/row_component.html.haml +5 -3
- data/app/components/tailwinds/table/row_component.rb +1 -1
- data/app/views/tramway/entities/index.html.haml +6 -3
- data/lib/tramway/decorators/class_helper.rb +19 -9
- data/lib/tramway/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbd54b0a803c53d12e50ec585a805ca39dc56b0551e12fa2de66514a9166b750
|
4
|
+
data.tar.gz: 7db6cfe3b24afaed951ac2ca2b52eb65d38bf0bdb49d6625f8508fa2dc594e52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4d807fffc40d7e2cdfd9fec0b513f39f4a4338cd9ea1718d4ece1d0587bc1d86aa26112688ec9fb5e108ed1a04fac01f7f79ae7852f105fb132e4b9361550a3
|
7
|
+
data.tar.gz: 45ac2aec489c85b212938ae99456fe8e5fc54f804cc9525aee1f034c5586d0e080e3c58fd9593f3779aabce5f2c5941ff36198183e81880f23f5d3a4b871aa98
|
data/README.md
CHANGED
@@ -486,6 +486,13 @@ eagerLoadControllersFrom("controllers", application)
|
|
486
486
|
application.register('multiselect', Multiselect) // register Multiselect controller class as `multiselect` stimulus controller
|
487
487
|
```
|
488
488
|
|
489
|
+
Use Stimulus `change` action with Tramway Multiselect
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
= tramway_form_for @user do |f|
|
493
|
+
= f.multiselect :role, data: { action: 'change->user-form#updateForm' } # user-form is your Stimulus controller, updateForm is a method inside user-form Stimulus controller
|
494
|
+
```
|
495
|
+
|
489
496
|
### Tailwind-styled pagination for Kaminari
|
490
497
|
|
491
498
|
Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari).
|
@@ -12,7 +12,8 @@ export default class Multiselect extends Controller {
|
|
12
12
|
selectedItems: Array,
|
13
13
|
placeholder: String,
|
14
14
|
selectAsInput: String,
|
15
|
-
value: Array
|
15
|
+
value: Array,
|
16
|
+
onChange: String
|
16
17
|
}
|
17
18
|
|
18
19
|
connect() {
|
@@ -26,9 +27,14 @@ export default class Multiselect extends Controller {
|
|
26
27
|
}
|
27
28
|
});
|
28
29
|
|
29
|
-
const initialValues = this.element.dataset.value === undefined ? [] : this.element.dataset.value
|
30
|
-
|
31
|
-
|
30
|
+
const initialValues = this.element.dataset.value === undefined ? [] : JSON.parse(this.element.dataset.value);
|
31
|
+
|
32
|
+
initialValues.map((value) => {
|
33
|
+
const itemIndex = this.items.findIndex(x => x.value.toString() === value.toString());
|
34
|
+
this.items[itemIndex].selected = true;
|
35
|
+
})
|
36
|
+
|
37
|
+
this.selectedItems = this.items.filter(item => item.selected);
|
32
38
|
|
33
39
|
this.renderSelectedItems();
|
34
40
|
}
|
@@ -84,6 +90,23 @@ export default class Multiselect extends Controller {
|
|
84
90
|
if (this.dropdown()) {
|
85
91
|
this.dropdown().remove();
|
86
92
|
}
|
93
|
+
|
94
|
+
const onChange = this.element.dataset.onChange;
|
95
|
+
|
96
|
+
if (onChange) {
|
97
|
+
const [controllerName, actionName] = onChange.split('#');
|
98
|
+
const controller = this.application.controllers.find(controller => controller.identifier === controllerName)
|
99
|
+
|
100
|
+
if (controller) {
|
101
|
+
if (typeof controller[actionName] === 'function') {
|
102
|
+
controller[actionName]({ target: this.element });
|
103
|
+
} else {
|
104
|
+
alert(`Action not found: ${actionName}`); // eslint-disable-line no-undef
|
105
|
+
}
|
106
|
+
} else {
|
107
|
+
alert(`Controller not found: ${controllerName}`); // eslint-disable-line no-undef
|
108
|
+
}
|
109
|
+
}
|
87
110
|
}
|
88
111
|
|
89
112
|
get template() {
|
@@ -38,7 +38,7 @@ module Tailwinds
|
|
38
38
|
def multiselect(attribute, collection, **options, &)
|
39
39
|
render(Tailwinds::Form::MultiselectComponent.new(
|
40
40
|
input: input(:text_field),
|
41
|
-
value: options[:value] || options[:selected] || object.public_send(attribute)
|
41
|
+
value: options[:value] || options[:selected] || object.public_send(attribute),
|
42
42
|
collection:,
|
43
43
|
**default_options(attribute, options)
|
44
44
|
), &)
|
@@ -15,12 +15,15 @@ module Tailwinds
|
|
15
15
|
def multiselect_hash
|
16
16
|
{
|
17
17
|
controller:, selected_item_template:, multiselect_selected_items_value:, dropdown_container:, item_container:,
|
18
|
-
items:, action:, select_as_input:, placeholder:, value:
|
18
|
+
items:, action:, select_as_input:, placeholder:, value:, on_change:
|
19
19
|
}.transform_keys { |key| key.to_s.gsub('_', '-') }
|
20
20
|
end
|
21
21
|
|
22
22
|
def controller
|
23
|
-
:multiselect
|
23
|
+
controllers = [:multiselect]
|
24
|
+
controllers << external_action.split('->').last.split('#').first if external_action
|
25
|
+
controllers += external_controllers
|
26
|
+
controllers.join(' ')
|
24
27
|
end
|
25
28
|
|
26
29
|
private
|
@@ -45,6 +48,20 @@ module Tailwinds
|
|
45
48
|
render(Tailwinds::Form::Multiselect::SelectAsInput.new(options:, attribute:, input:))
|
46
49
|
end
|
47
50
|
|
51
|
+
def on_change
|
52
|
+
return unless external_action&.start_with?('change')
|
53
|
+
|
54
|
+
external_action.split('->').last
|
55
|
+
end
|
56
|
+
|
57
|
+
def external_controllers
|
58
|
+
options[:controller]&.split || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def external_action
|
62
|
+
options.dig(:data, :action)
|
63
|
+
end
|
64
|
+
|
48
65
|
def method_missing(method_name, *, &)
|
49
66
|
component = component_name(method_name)
|
50
67
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
%nav.py-
|
1
|
+
%nav.py-2.px-4.sm:px-8.flex.justify-between.items-center.dark:bg-gray-800{ class: "bg-#{@color}" }
|
2
2
|
.flex.justify-between.w-full
|
3
3
|
- if @title[:text].present? || @left_items.present?
|
4
4
|
.flex.items-center
|
@@ -7,16 +7,16 @@
|
|
7
7
|
.text-xl.text-white.font-bold
|
8
8
|
= @title[:text]
|
9
9
|
- if @left_items.present?
|
10
|
-
%ul.
|
10
|
+
%ul.block.flex.flex-row.items-center.space-x-4.ml-4
|
11
11
|
- @left_items.each do |item|
|
12
12
|
= item
|
13
13
|
|
14
|
-
.
|
14
|
+
.hidden.sm:block
|
15
15
|
%button#mobile-menu-button.text-white.focus:outline-none
|
16
16
|
☰
|
17
17
|
|
18
18
|
- if @right_items.present?
|
19
|
-
%ul.
|
19
|
+
%ul.block.sm:flex.items-center.space-x-4
|
20
20
|
- @right_items.each do |item|
|
21
21
|
= item
|
22
22
|
|
@@ -1,3 +1,10 @@
|
|
1
1
|
- unless current_page.first?
|
2
|
-
= link_to helpers.t('views.pagination.first').html_safe,
|
3
|
-
|
2
|
+
= link_to helpers.t('views.pagination.first').html_safe,
|
3
|
+
url,
|
4
|
+
remote:,
|
5
|
+
class: pagination_classes(klass: 'first sm:hidden flex')
|
6
|
+
|
7
|
+
= link_to '⭰',
|
8
|
+
url,
|
9
|
+
remote:,
|
10
|
+
class: pagination_classes(klass: 'first hidden sm:flex font-bold')
|
@@ -1,2 +1,2 @@
|
|
1
|
-
%span.page.gap.px-3.py-2.text-sm.font-medium.text-purple-700.dark:text-white.sm:
|
1
|
+
%span.page.gap.px-3.py-2.text-sm.font-medium.text-purple-700.dark:text-white.flex.sm:hidden
|
2
2
|
= helpers.t('views.pagination.truncate').html_safe
|
@@ -1,3 +1,10 @@
|
|
1
1
|
- unless current_page.last?
|
2
|
-
= link_to helpers.t('views.pagination.last').html_safe,
|
3
|
-
|
2
|
+
= link_to helpers.t('views.pagination.last').html_safe,
|
3
|
+
url,
|
4
|
+
remote:,
|
5
|
+
class: pagination_classes(klass: 'last sm:hidden flex')
|
6
|
+
|
7
|
+
= link_to '⭲',
|
8
|
+
url,
|
9
|
+
remote:,
|
10
|
+
class: pagination_classes(klass: 'last hidden sm:flex font-bold')
|
@@ -1,3 +1,12 @@
|
|
1
1
|
- unless current_page.last?
|
2
|
-
= link_to helpers.t('views.pagination.next').html_safe,
|
3
|
-
|
2
|
+
= link_to helpers.t('views.pagination.next').html_safe,
|
3
|
+
url,
|
4
|
+
rel: 'next',
|
5
|
+
remote:,
|
6
|
+
class: pagination_classes(klass: 'next sm:hidden flex')
|
7
|
+
|
8
|
+
= link_to '🠖',
|
9
|
+
url,
|
10
|
+
rel: 'next',
|
11
|
+
remote:,
|
12
|
+
class: pagination_classes(klass: 'next hidden sm:flex font-bold')
|
@@ -2,4 +2,8 @@
|
|
2
2
|
%span.px-3.py-2.font-medium.rounded-md.bg-purple-500.text-white.dark:text-gray-800.dark:bg-white
|
3
3
|
= page
|
4
4
|
- else
|
5
|
-
= link_to page,
|
5
|
+
= link_to page,
|
6
|
+
url,
|
7
|
+
remote:,
|
8
|
+
rel: page.rel,
|
9
|
+
class: pagination_classes(klass: 'sm:hidden flex')
|
@@ -1,3 +1,12 @@
|
|
1
1
|
- unless current_page.first?
|
2
|
-
= link_to helpers.t('views.pagination.previous').html_safe,
|
3
|
-
|
2
|
+
= link_to helpers.t('views.pagination.previous').html_safe,
|
3
|
+
url,
|
4
|
+
rel: 'prev',
|
5
|
+
remote:,
|
6
|
+
class: pagination_classes(klass: 'prev sm:hidden flex')
|
7
|
+
|
8
|
+
= link_to '🠔',
|
9
|
+
url,
|
10
|
+
rel: 'prev',
|
11
|
+
remote:,
|
12
|
+
class: pagination_classes(klass: 'prev hidden sm:flex font-bold')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
- cols = headers.map { |item| "1fr" }.join(",")
|
2
2
|
|
3
|
-
.div-table-row.
|
3
|
+
.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-[#{cols}]" }
|
4
4
|
- headers.each do |header|
|
5
5
|
.div-table-cell.py-4.px-6
|
6
6
|
= header
|
@@ -2,12 +2,14 @@
|
|
2
2
|
- if cells.any?
|
3
3
|
- cols = cells.count.times.map { |item| "1fr" }.join(",")
|
4
4
|
|
5
|
-
|
5
|
+
-# desktop view
|
6
|
+
= row_tag class: "div-table-row block grid gap-4 bg-white border-b dark:bg-gray-800 dark:border-gray-700 grid-cols-[#{cols}]" do
|
6
7
|
- cells.each do |(_, value)|
|
7
|
-
.div-table-cell.px-6.py-4.font-medium.text-gray-900.whitespace-nowrap.dark:text-white.text-xs.
|
8
|
+
.div-table-cell.px-6.py-4.font-medium.text-gray-900.whitespace-nowrap.dark:text-white.sm:text-xs.text-base
|
8
9
|
= value
|
9
10
|
|
10
|
-
|
11
|
+
-# mobile view
|
12
|
+
.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 }
|
11
13
|
.w-full.p-4.bg-purple-100.text-gray-700.dark:bg-gray-700.dark:text-gray-400
|
12
14
|
= cells.values.first
|
13
15
|
|
@@ -9,7 +9,7 @@ module Tailwinds
|
|
9
9
|
|
10
10
|
def row_tag(**options, &)
|
11
11
|
if href.present?
|
12
|
-
klass = "#{options[:class] || ''} cursor-pointer hover:bg-gray-700"
|
12
|
+
klass = "#{options[:class] || ''} cursor-pointer dark:hover:bg-gray-700"
|
13
13
|
|
14
14
|
link_to(href, options.merge(class: klass)) do
|
15
15
|
yield if block_given?
|
@@ -1,3 +1,6 @@
|
|
1
|
+
- decorator = Tramway::Decorators::NameBuilder.default_decorator_class_name(@model_class)
|
2
|
+
- list_attributes = decorator.constantize.list_attributes
|
3
|
+
|
1
4
|
.w-full
|
2
5
|
- content_for :title, page_title
|
3
6
|
|
@@ -8,13 +11,13 @@
|
|
8
11
|
- if Tramway.config.pagination[:enabled]
|
9
12
|
= paginate @entities
|
10
13
|
|
11
|
-
- if
|
14
|
+
- if list_attributes.empty?
|
12
15
|
%p.text-center.mt-10
|
13
16
|
You should fill class-level method `self.list_attributes` inside your
|
14
|
-
=
|
17
|
+
= decorator
|
15
18
|
|
16
19
|
= component 'tailwinds/table' do
|
17
|
-
= component 'tailwinds/table/header', headers:
|
20
|
+
= component 'tailwinds/table/header', headers: list_attributes.map { |attribute| @model_class.human_attribute_name(attribute) }
|
18
21
|
- @entities.each do |item|
|
19
22
|
= render 'entity', entity: item
|
20
23
|
|
@@ -7,15 +7,15 @@ module Tramway
|
|
7
7
|
module_function
|
8
8
|
|
9
9
|
def decorator_class(object_or_array, decorator = nil)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
raise_error_if_object_empty object_or_array, decorator
|
11
|
+
|
12
|
+
return decorator if decorator.present?
|
13
|
+
|
14
|
+
begin
|
15
|
+
class_name = decorator_class_name(object_or_array)
|
16
|
+
class_name.constantize
|
17
|
+
rescue NameError
|
18
|
+
raise NameError, "You should define #{class_name} decorator class."
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -28,6 +28,16 @@ module Tramway
|
|
28
28
|
|
29
29
|
Tramway::Decorators::NameBuilder.default_decorator_class_name(klass)
|
30
30
|
end
|
31
|
+
|
32
|
+
# :reek:NilCheck { enabled: false }
|
33
|
+
def raise_error_if_object_empty(object_or_array, decorator)
|
34
|
+
return unless object_or_array.blank? && decorator.nil?
|
35
|
+
|
36
|
+
text = 'You should pass object or array that is not empty OR provide a decorator class as a second argument'
|
37
|
+
|
38
|
+
raise ArgumentError, text
|
39
|
+
end
|
40
|
+
# :reek:NilCheck { enabled: true }
|
31
41
|
end
|
32
42
|
end
|
33
43
|
end
|
data/lib/tramway/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tramway
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kalashnikovisme
|
8
8
|
- moshiaan
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-01-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: anyway_config
|
@@ -213,7 +213,7 @@ metadata:
|
|
213
213
|
homepage_uri: https://github.com/purple-magic/tramway
|
214
214
|
source_code_uri: https://github.com/purple-magic/tramway
|
215
215
|
changelog_uri: https://github.com/purple-magic/tramway
|
216
|
-
post_install_message:
|
216
|
+
post_install_message:
|
217
217
|
rdoc_options: []
|
218
218
|
require_paths:
|
219
219
|
- lib
|
@@ -229,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
229
229
|
version: '0'
|
230
230
|
requirements: []
|
231
231
|
rubygems_version: 3.4.6
|
232
|
-
signing_key:
|
232
|
+
signing_key:
|
233
233
|
specification_version: 4
|
234
234
|
summary: Tramway Rails Engine
|
235
235
|
test_files: []
|