tramway 0.4.8 → 0.4.9.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 +44 -1
- data/app/assets/javascripts/tramway/multiselect_controller.js +131 -0
- data/app/components/tailwinds/form/builder.rb +11 -2
- data/app/components/tailwinds/form/multiselect/dropdown_container.html.haml +3 -0
- data/app/components/tailwinds/form/multiselect/dropdown_container.rb +11 -0
- data/app/components/tailwinds/form/multiselect/item_container.html.haml +5 -0
- data/app/components/tailwinds/form/multiselect/item_container.rb +11 -0
- data/app/components/tailwinds/form/multiselect/select_as_input.html.haml +2 -0
- data/app/components/tailwinds/form/multiselect/select_as_input.rb +16 -0
- data/app/components/tailwinds/form/multiselect/selected_item_template.html.haml +6 -0
- data/app/components/tailwinds/form/multiselect/selected_item_template.rb +11 -0
- data/app/components/tailwinds/form/multiselect_component.html.haml +10 -0
- data/app/components/tailwinds/form/multiselect_component.rb +73 -0
- data/app/components/tailwinds/form/select_component.rb +1 -1
- data/config/routes.rb +2 -0
- data/lib/rules/turbo_html_attributes_rules.rb +1 -1
- data/lib/tramway/configs/entity.rb +15 -8
- data/lib/tramway/engine.rb +4 -0
- data/lib/tramway/helpers/navbar_helper.rb +1 -1
- data/lib/tramway/helpers/views_helper.rb +2 -2
- data/lib/tramway/navbar.rb +7 -7
- data/lib/tramway/utils/render.rb +2 -2
- data/lib/tramway/version.rb +1 -1
- metadata +13 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 588137c9cafa9b3b22de988ed0593b5ad34bda6480919b99b6eb7899cd8efb14
|
4
|
+
data.tar.gz: dbd1ecd2af4158892bb2e517cb3c962143f4f1109a00b8f392eaae392ad42205
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f3a4841ba2bdbe9e5bb1a12ab10900b90d43b916534af3f33eb57e6be0a33ae88f6148f7c795d584f7a9ac7cadc7b7bc38b411f8ca6694b0a7d23153295f7d8
|
7
|
+
data.tar.gz: 215ba73a338b8ab3d30b923696bb9cc22f189f9c8f1858c2e2a6cec0ce388e4fb4f30fee0345deaab19677416810f35935fa62b658b2c8f6b176e0d02da5a337
|
data/README.md
CHANGED
@@ -8,7 +8,9 @@ Unite Ruby on Rails brilliance. Streamline development with Tramway.
|
|
8
8
|
* [Tramway Form](https://github.com/Purple-Magic/tramway#tramway-form)
|
9
9
|
* [Tramway Navbar](https://github.com/Purple-Magic/tramway#tramway-navbar)
|
10
10
|
* [Tailwind-styled forms](https://github.com/Purple-Magic/tramway#tailwind-styled-forms)
|
11
|
+
* [Stimulus-based inputs](https://github.com/Purple-Magic/tramway#stimulus-based-inputs)
|
11
12
|
* [Tailwind-styled pagination](https://github.com/Purple-Magic/tramway?tab=readme-ov-file#tailwind-styled-pagination-for-kaminari)
|
13
|
+
* [Articles](https://github.com/Purple-Magic/tramway#usage)
|
12
14
|
|
13
15
|
## Installation
|
14
16
|
Add this line to your application's Gemfile:
|
@@ -421,10 +423,11 @@ Tramway uses [Tailwind](https://tailwindcss.com/) by default. All UI helpers are
|
|
421
423
|
Tramway provides `tramway_form_for` helper that renders Tailwind-styled forms by default.
|
422
424
|
|
423
425
|
```ruby
|
424
|
-
= tramway_form_for
|
426
|
+
= tramway_form_for @user do |f|
|
425
427
|
= f.text_field :text
|
426
428
|
= f.password_field :password
|
427
429
|
= f.select :role, [:admin, :user]
|
430
|
+
= f.multiselect :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']]
|
428
431
|
= f.file_field :file
|
429
432
|
= f.submit "Create User"
|
430
433
|
```
|
@@ -436,8 +439,42 @@ Available form helpers:
|
|
436
439
|
* password_field
|
437
440
|
* file_field
|
438
441
|
* select
|
442
|
+
* multiselect ([Stimulus-based](https://github.com/Purple-Magic/tramway#stimulus-based-inputs))
|
439
443
|
* submit
|
440
444
|
|
445
|
+
#### Stimulus-based inputs
|
446
|
+
|
447
|
+
`tramway_form_for` provides Tailwind-styled Stimulus-based custom inputs.
|
448
|
+
|
449
|
+
##### Multiselect
|
450
|
+
|
451
|
+
In case you want to use tailwind-styled multiselect this way
|
452
|
+
|
453
|
+
```haml
|
454
|
+
= tramway_form_for @user do |f|
|
455
|
+
= f.multiselect :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']]
|
456
|
+
#- ...
|
457
|
+
```
|
458
|
+
|
459
|
+
you should add Tramway Multiselect Stimulus controller to your application.
|
460
|
+
|
461
|
+
Example for [importmap-rails](https://github.com/rails/importmap-rails) config
|
462
|
+
|
463
|
+
*config/importmap.rb*
|
464
|
+
```ruby
|
465
|
+
pin '@tramway/multiselect', to: 'tramway/multiselect_controller.js'
|
466
|
+
```
|
467
|
+
|
468
|
+
*app/javascript/controllers/index.js*
|
469
|
+
```js
|
470
|
+
import { application } from "controllers/application"
|
471
|
+
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
|
472
|
+
import { Multiselect } from "@tramway/multiselect" // importing Multiselect controller class
|
473
|
+
eagerLoadControllersFrom("controllers", application)
|
474
|
+
|
475
|
+
application.register('multiselect', Multiselect) // register Multiselect controller class as `multiselect` stimulus controller
|
476
|
+
```
|
477
|
+
|
441
478
|
### Tailwind-styled pagination for Kaminari
|
442
479
|
|
443
480
|
Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari).
|
@@ -480,6 +517,12 @@ user_2 = tramway_form User.first
|
|
480
517
|
user_2.object #=> returns pure user object
|
481
518
|
```
|
482
519
|
|
520
|
+
## Articles
|
521
|
+
* [Tramway on Rails](https://kalashnikovisme.medium.com/tramway-on-rails-32158c35ed68)
|
522
|
+
* [Delegating ActiveRecord methods to decorators in Rails](https://kalashnikovisme.medium.com/delegating-activerecord-methods-to-decorators-in-rails-4e4ec1c6b3a6)
|
523
|
+
* [Behave as ActiveRecord. Why do we want objects to be AR lookalikes?](https://kalashnikovisme.medium.com/behave-as-activerecord-why-do-we-want-objects-to-be-ar-lookalikes-d494d692e1d3)
|
524
|
+
* [Decorating associations in Rails with Tramway](https://kalashnikovisme.medium.com/decorating-associations-in-rails-with-tramway-b46a28392f9e)
|
525
|
+
|
483
526
|
## Contributing
|
484
527
|
|
485
528
|
Install [lefthook](https://github.com/evilmartians/lefthook)
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
2
|
+
|
3
|
+
export default class Multiselect extends Controller {
|
4
|
+
static targets = ["dropdown", "showSelectedArea", "hiddenInput"];
|
5
|
+
|
6
|
+
static values = {
|
7
|
+
items: Array,
|
8
|
+
dropdownContainer: String,
|
9
|
+
itemContainer: String,
|
10
|
+
selectedItemTemplate: String,
|
11
|
+
dropdownState: String,
|
12
|
+
selectedItems: Array,
|
13
|
+
placeholder: String,
|
14
|
+
selectAsInput: String,
|
15
|
+
value: Array
|
16
|
+
}
|
17
|
+
|
18
|
+
connect() {
|
19
|
+
this.dropdownState = 'closed';
|
20
|
+
this.unselectedItems = JSON.parse(this.element.dataset.items).map((item) => {
|
21
|
+
return {
|
22
|
+
text: item.text,
|
23
|
+
value: item.value.toString()
|
24
|
+
}
|
25
|
+
});
|
26
|
+
|
27
|
+
const initialValues = this.element.dataset.value === undefined ? [] : this.element.dataset.value.split(',')
|
28
|
+
this.selectedItems = this.unselectedItems.filter(item => initialValues.includes(item.value));
|
29
|
+
this.unselectedItems = this.unselectedItems.filter(item => !initialValues.includes(item.value));
|
30
|
+
|
31
|
+
this.renderSelectedItems();
|
32
|
+
}
|
33
|
+
|
34
|
+
renderSelectedItems() {
|
35
|
+
const allItems = this.fillTemplate(this.element.dataset.selectedItemTemplate, this.selectedItems);
|
36
|
+
this.showSelectedAreaTarget.innerHTML = allItems;
|
37
|
+
this.showSelectedAreaTarget.insertAdjacentHTML("beforeEnd", this.input());
|
38
|
+
this.updateInputOptions();
|
39
|
+
}
|
40
|
+
|
41
|
+
fillTemplate(template, items) {
|
42
|
+
return items.map((item) => {
|
43
|
+
return template.replace(/{{text}}/g, item.text).replace(/{{value}}/g, item.value)
|
44
|
+
}).join('')
|
45
|
+
}
|
46
|
+
|
47
|
+
closeOnClickOutside(event) {
|
48
|
+
if (this.dropdownState === 'open' && !this.element.contains(event.target)) {
|
49
|
+
this.closeDropdown();
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
toggleDropdown() {
|
54
|
+
if (this.dropdownState === 'closed') {
|
55
|
+
this.openDropdown();
|
56
|
+
} else {
|
57
|
+
this.closeDropdown();
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
rerenderItems() {
|
62
|
+
this.closeDropdown();
|
63
|
+
this.openDropdown();
|
64
|
+
}
|
65
|
+
|
66
|
+
openDropdown() {
|
67
|
+
this.dropdownState = 'open';
|
68
|
+
this.dropdownTarget.insertAdjacentHTML("afterend", this.template);
|
69
|
+
|
70
|
+
if (this.dropdown()) {
|
71
|
+
this.dropdown().addEventListener('click', event => event.stopPropagation());
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
dropdown() {
|
76
|
+
return this.element.querySelector('#dropdown');
|
77
|
+
}
|
78
|
+
|
79
|
+
closeDropdown() {
|
80
|
+
this.dropdownState = 'closed';
|
81
|
+
if (this.dropdown()) {
|
82
|
+
this.dropdown().remove();
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
get template() {
|
87
|
+
return this.element.dataset.dropdownContainer.replace(
|
88
|
+
/{{content}}/g,
|
89
|
+
this.fillTemplate(this.element.dataset.itemContainer, this.unselectedItems)
|
90
|
+
);
|
91
|
+
}
|
92
|
+
|
93
|
+
toggleItem({ currentTarget }) {
|
94
|
+
const item = {
|
95
|
+
text: currentTarget.dataset.text,
|
96
|
+
value: currentTarget.dataset.value
|
97
|
+
};
|
98
|
+
|
99
|
+
const itemIndex = this.selectedItems.findIndex(x => x.value === item.value);
|
100
|
+
if (itemIndex !== -1) {
|
101
|
+
this.selectedItems = this.selectedItems.filter((_, index) => index !== itemIndex);
|
102
|
+
} else {
|
103
|
+
this.selectedItems.push(item);
|
104
|
+
}
|
105
|
+
|
106
|
+
this.unselectedItems = this.unselectedItems.filter(x => x.value !== item.value);
|
107
|
+
|
108
|
+
this.renderSelectedItems();
|
109
|
+
this.rerenderItems();
|
110
|
+
}
|
111
|
+
|
112
|
+
input() {
|
113
|
+
const placeholder = this.selectedItems.length > 0 ? '' : this.element.dataset.placeholder;
|
114
|
+
return this.element.dataset.selectAsInput.replace(/{{placeholder}}/g, placeholder);
|
115
|
+
}
|
116
|
+
|
117
|
+
updateInputOptions() {
|
118
|
+
this.hiddenInputTarget.innerHTML = '';
|
119
|
+
this.selectedItems.forEach(selected => {
|
120
|
+
const option = document.createElement("option");
|
121
|
+
option.text = selected.text;
|
122
|
+
option.value = selected.value;
|
123
|
+
option.setAttribute("selected", true);
|
124
|
+
this.hiddenInputTarget.append(option);
|
125
|
+
});
|
126
|
+
|
127
|
+
this.hiddenInputTarget.value = this.selectedItems.map(item => item.value);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
export { Multiselect }
|
@@ -35,8 +35,17 @@ module Tailwinds
|
|
35
35
|
), &)
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
render(Tailwinds::Form::
|
38
|
+
def multiselect(attribute, collection, **options, &)
|
39
|
+
render(Tailwinds::Form::MultiselectComponent.new(
|
40
|
+
input: input(:text_field),
|
41
|
+
value: options[:value] || options[:selected] || object.public_send(attribute)&.first,
|
42
|
+
collection:,
|
43
|
+
**default_options(attribute, options)
|
44
|
+
), &)
|
45
|
+
end
|
46
|
+
|
47
|
+
def submit(action, **, &)
|
48
|
+
render(Tailwinds::Form::SubmitButtonComponent.new(action, **), &)
|
40
49
|
end
|
41
50
|
|
42
51
|
private
|
@@ -0,0 +1,5 @@
|
|
1
|
+
.cursor-pointer.w-full.border-gray-100.rounded-t.border-b.hover:bg-teal-100{ data: { action: "click->multiselect#toggleItem", text: "{{text}}", value: "{{value}}" } }
|
2
|
+
.flex.w-full.items-center.p-2.pl-2.border-transparent.border-l-2.relative.hover:border-teal-100
|
3
|
+
.w-full.items-center.flex
|
4
|
+
.mx-2.leading-6
|
5
|
+
{{text}}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tailwinds
|
4
|
+
module Form
|
5
|
+
module Multiselect
|
6
|
+
# Renders input as select
|
7
|
+
class SelectAsInput < ViewComponent::Base
|
8
|
+
extend Dry::Initializer[undefined: false]
|
9
|
+
|
10
|
+
option :options
|
11
|
+
option :attribute
|
12
|
+
option :input
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
.flex.justify-center.items-center.m-1.font-medium.py-1.px-2.bg-white.rounded-full.text-teal-700.bg-teal-100.border.border-teal-300
|
2
|
+
.text-xs.font-normal.leading-none.max-w-full.flex-initial
|
3
|
+
{{text}}
|
4
|
+
.flex.flex-auto.flex-row-reverse
|
5
|
+
.cursor-pointer{ data: { action: "click->multiselect#toggleItem", text: "{{text}}", value: "{{value}}" } }
|
6
|
+
⨯
|
@@ -0,0 +1,10 @@
|
|
1
|
+
.mb-4
|
2
|
+
- if @label
|
3
|
+
%label.block.text-gray-700.text-sm.font-bold.mb-2{ for: @for }
|
4
|
+
= @label
|
5
|
+
.flex.flex-col.items-center.relative{ data: multiselect_hash, id: "#{@for}_multiselect" }
|
6
|
+
.min-w-96.w-fit
|
7
|
+
.p-1.flex.border.border-gray-200.bg-white.rounded{ data: { "multiselect-target" => "dropdown" } }
|
8
|
+
.flex.flex-auto.flex-wrap{ data: { "multiselect-target" => "showSelectedArea" } }
|
9
|
+
.text-gray-300.w-8.py-1.pl-2.pr-1.border-l.flex.items-center.border-gray-200
|
10
|
+
^
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tailwinds
|
4
|
+
module Form
|
5
|
+
# Tailwind-styled multi-select field
|
6
|
+
class MultiselectComponent < TailwindComponent
|
7
|
+
option :collection
|
8
|
+
|
9
|
+
def before_render
|
10
|
+
@collection = collection.map do |(text, value)|
|
11
|
+
{ text:, value: }
|
12
|
+
end.to_json
|
13
|
+
end
|
14
|
+
|
15
|
+
def multiselect_hash
|
16
|
+
{
|
17
|
+
controller:, selected_item_template:, multiselect_selected_items_value:, dropdown_container:, item_container:,
|
18
|
+
items:, action:, select_as_input:, placeholder:, value:
|
19
|
+
}.transform_keys { |key| key.to_s.gsub('_', '-') }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def controller
|
25
|
+
:multiselect
|
26
|
+
end
|
27
|
+
|
28
|
+
def action
|
29
|
+
'click->multiselect#toggleDropdown'
|
30
|
+
end
|
31
|
+
|
32
|
+
def items
|
33
|
+
collection
|
34
|
+
end
|
35
|
+
|
36
|
+
def placeholder
|
37
|
+
options[:placeholder]
|
38
|
+
end
|
39
|
+
|
40
|
+
def multiselect_selected_items_value
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
44
|
+
def select_as_input
|
45
|
+
render(Tailwinds::Form::Multiselect::SelectAsInput.new(options:, attribute:, input:))
|
46
|
+
end
|
47
|
+
|
48
|
+
def method_missing(method_name, *, &)
|
49
|
+
component = component_name(method_name)
|
50
|
+
|
51
|
+
if method_name.to_s.include?('_') && Object.const_defined?(component)
|
52
|
+
render(component.constantize.new(*, &))
|
53
|
+
else
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def respond_to_missing?(method_name, include_private = false)
|
59
|
+
if method_name.to_s.include?('_') && Object.const_defined?(component_name(method_name))
|
60
|
+
true
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# :reek:UtilityFunction { enabled: false }
|
67
|
+
def component_name(method_name)
|
68
|
+
"Tailwinds::Form::Multiselect::#{method_name.to_s.camelize}"
|
69
|
+
end
|
70
|
+
# :reek:UtilityFunction { enabled: true }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/config/routes.rb
CHANGED
@@ -9,18 +9,25 @@ module Tramway
|
|
9
9
|
attribute :name, Types::Coercible::String
|
10
10
|
attribute? :route, Tramway::Configs::Entities::Route
|
11
11
|
|
12
|
+
# Route Struct contains implemented in Tramway CRUD and helpful routes for the entity
|
13
|
+
RouteStruct = Struct.new(:index)
|
14
|
+
|
15
|
+
# HumanNameStruct contains human names forms for the entity
|
16
|
+
HumanNameStruct = Struct.new(:single, :plural)
|
17
|
+
|
12
18
|
def routes
|
13
|
-
|
19
|
+
RouteStruct.new(Rails.application.routes.url_helpers.public_send(route_helper_method))
|
14
20
|
end
|
15
21
|
|
16
22
|
def human_name
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
single, plural = if model_class.present?
|
24
|
+
model_name = model_class.model_name.human
|
25
|
+
[model_name, model_name.pluralize]
|
26
|
+
else
|
27
|
+
[name.capitalize, name.pluralize.capitalize]
|
28
|
+
end
|
29
|
+
|
30
|
+
HumanNameStruct.new(single, plural)
|
24
31
|
end
|
25
32
|
|
26
33
|
private
|
data/lib/tramway/engine.rb
CHANGED
@@ -4,8 +4,8 @@ module Tramway
|
|
4
4
|
module Helpers
|
5
5
|
# Provides view-oriented helpers for ActionView
|
6
6
|
module ViewsHelper
|
7
|
-
def tramway_form_for(object,
|
8
|
-
form_for(object,
|
7
|
+
def tramway_form_for(object, *, **options, &)
|
8
|
+
form_for(object, *, **options.merge(builder: Tailwinds::Form::Builder), &)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/tramway/navbar.rb
CHANGED
@@ -33,13 +33,13 @@ module Tramway
|
|
33
33
|
reset_filling
|
34
34
|
end
|
35
35
|
|
36
|
-
def item(text_or_url, url = nil,
|
37
|
-
raise 'You cannot provide an argument and a code block at the same time' if provided_url_and_block?(url, &
|
36
|
+
def item(text_or_url, url = nil, **, &)
|
37
|
+
raise 'You cannot provide an argument and a code block at the same time' if provided_url_and_block?(url, &)
|
38
38
|
|
39
39
|
rendered_item = if url.present?
|
40
|
-
render_ignoring_block(text_or_url, url, **
|
40
|
+
render_ignoring_block(text_or_url, url, **)
|
41
41
|
else
|
42
|
-
render_using_block(text_or_url,
|
42
|
+
render_using_block(text_or_url, **, &)
|
43
43
|
end
|
44
44
|
|
45
45
|
@items[@filling] << rendered_item
|
@@ -79,13 +79,13 @@ module Tramway
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
def render_using_block(text_or_url, method: nil, **options, &
|
82
|
+
def render_using_block(text_or_url, method: nil, **options, &)
|
83
83
|
options.merge!(href: text_or_url)
|
84
84
|
|
85
85
|
if method.present? && method.to_sym != :get
|
86
|
-
context.render(Tailwinds::Nav::Item::ButtonComponent.new(method:, **options), &
|
86
|
+
context.render(Tailwinds::Nav::Item::ButtonComponent.new(method:, **options), &)
|
87
87
|
else
|
88
|
-
context.render(Tailwinds::Nav::Item::LinkComponent.new(method:, **options), &
|
88
|
+
context.render(Tailwinds::Nav::Item::LinkComponent.new(method:, **options), &)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
data/lib/tramway/utils/render.rb
CHANGED
@@ -5,8 +5,8 @@ module Tramway
|
|
5
5
|
# Provides helper method render that depends on ActionController::Base.render method
|
6
6
|
#
|
7
7
|
module Render
|
8
|
-
def render(
|
9
|
-
ActionController::Base.render(
|
8
|
+
def render(*, &)
|
9
|
+
ActionController::Base.render(*, &)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
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: 0.4.
|
4
|
+
version: 0.4.9.1
|
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: 2024-
|
12
|
+
date: 2024-09-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dry-struct
|
@@ -67,20 +67,6 @@ dependencies:
|
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: rspec-rails
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: '0'
|
77
|
-
type: :development
|
78
|
-
prerelease: false
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - ">="
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: '0'
|
84
70
|
description: Tramway Rails Engine
|
85
71
|
email:
|
86
72
|
- kalashnikovisme@gmail.com
|
@@ -91,11 +77,22 @@ files:
|
|
91
77
|
- MIT-LICENSE
|
92
78
|
- README.md
|
93
79
|
- Rakefile
|
80
|
+
- app/assets/javascripts/tramway/multiselect_controller.js
|
94
81
|
- app/components/tailwind_component.html.haml
|
95
82
|
- app/components/tailwind_component.rb
|
96
83
|
- app/components/tailwinds/form/builder.rb
|
97
84
|
- app/components/tailwinds/form/file_field_component.html.haml
|
98
85
|
- app/components/tailwinds/form/file_field_component.rb
|
86
|
+
- app/components/tailwinds/form/multiselect/dropdown_container.html.haml
|
87
|
+
- app/components/tailwinds/form/multiselect/dropdown_container.rb
|
88
|
+
- app/components/tailwinds/form/multiselect/item_container.html.haml
|
89
|
+
- app/components/tailwinds/form/multiselect/item_container.rb
|
90
|
+
- app/components/tailwinds/form/multiselect/select_as_input.html.haml
|
91
|
+
- app/components/tailwinds/form/multiselect/select_as_input.rb
|
92
|
+
- app/components/tailwinds/form/multiselect/selected_item_template.html.haml
|
93
|
+
- app/components/tailwinds/form/multiselect/selected_item_template.rb
|
94
|
+
- app/components/tailwinds/form/multiselect_component.html.haml
|
95
|
+
- app/components/tailwinds/form/multiselect_component.rb
|
99
96
|
- app/components/tailwinds/form/select_component.html.haml
|
100
97
|
- app/components/tailwinds/form/select_component.rb
|
101
98
|
- app/components/tailwinds/form/submit_button_component.html.haml
|