tramway 0.4.8 → 0.4.9.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e496f3ab6336d7f29b0b0021ef04b2bc18353ca3049406dd59671fbb8638d48f
4
- data.tar.gz: 7863120a81d6195f4e60bcae4d24a43845a6a184621bfe10ecc4dfcb8c83ff4c
3
+ metadata.gz: 17ef05a91356ce84f217df49af7245c2df48abd356625f7c99e2d96f693d5fb7
4
+ data.tar.gz: 002da5b891c4eea585c317b006aface8f1443652ec1700f02078d0a47a920068
5
5
  SHA512:
6
- metadata.gz: 374c6d983df5d172bf08e9f33fecb37abe192e4e6b8fd0a8863c3cd367f77206238abf05edfa7c0501919c860d90ee790fa16be721bc4c70adb2ad51245170fe
7
- data.tar.gz: 6876ad8aef86197b3b56c64cc07eddfcc9943aaa504d20b3d970d55e2fe197df16d6b5d28876d63cee75bca26e287fcc2ed078e3e420c5958607eac27fe78abe
6
+ metadata.gz: 3a0c3cb1ce60c537818e0fe62812128f15e8f1dcdf6f523d60b6b93e3413a20e4b9b19f7a1228b18b7a7e253083775a3244c6dfd2a80034d2bbb5e3ebf23e746
7
+ data.tar.gz: e0cfa260fb65d82fa5e0928ada86e01b1097b213f83fb2b5907e75f6c0e93b9ea196a682be2fb30b5058f1802b53a026a909b33138b6f08019d0918e2981f0d0
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:
@@ -363,6 +365,17 @@ tramway_navbar title: 'Purple Magic', background: { color: :red, intensity: 500
363
365
  end
364
366
  ```
365
367
 
368
+ # Haml example
369
+
370
+ ```haml
371
+ = tramway_navbar title: 'Purple Magic', background: { color: :red, intensity: 500 } do |nav|
372
+ - nav.left do
373
+ - nav.item 'Users', '/users'
374
+ - nav.item 'Podcasts', '/podcasts'
375
+ - nav.right do
376
+ - nav.item 'Sign out', '/users/sessions', method: :delete, confirm: 'Wanna quit?'
377
+ ```
378
+
366
379
  will render [this](https://play.tailwindcss.com/UZPTCudFw5)
367
380
 
368
381
  #### tramway_navbar
@@ -421,10 +434,11 @@ Tramway uses [Tailwind](https://tailwindcss.com/) by default. All UI helpers are
421
434
  Tramway provides `tramway_form_for` helper that renders Tailwind-styled forms by default.
422
435
 
423
436
  ```ruby
424
- = tramway_form_for User.new do |f|
437
+ = tramway_form_for @user do |f|
425
438
  = f.text_field :text
426
439
  = f.password_field :password
427
440
  = f.select :role, [:admin, :user]
441
+ = f.multiselect :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']]
428
442
  = f.file_field :file
429
443
  = f.submit "Create User"
430
444
  ```
@@ -436,8 +450,42 @@ Available form helpers:
436
450
  * password_field
437
451
  * file_field
438
452
  * select
453
+ * multiselect ([Stimulus-based](https://github.com/Purple-Magic/tramway#stimulus-based-inputs))
439
454
  * submit
440
455
 
456
+ #### Stimulus-based inputs
457
+
458
+ `tramway_form_for` provides Tailwind-styled Stimulus-based custom inputs.
459
+
460
+ ##### Multiselect
461
+
462
+ In case you want to use tailwind-styled multiselect this way
463
+
464
+ ```haml
465
+ = tramway_form_for @user do |f|
466
+ = f.multiselect :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']]
467
+ #- ...
468
+ ```
469
+
470
+ you should add Tramway Multiselect Stimulus controller to your application.
471
+
472
+ Example for [importmap-rails](https://github.com/rails/importmap-rails) config
473
+
474
+ *config/importmap.rb*
475
+ ```ruby
476
+ pin '@tramway/multiselect', to: 'tramway/multiselect_controller.js'
477
+ ```
478
+
479
+ *app/javascript/controllers/index.js*
480
+ ```js
481
+ import { application } from "controllers/application"
482
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
483
+ import { Multiselect } from "@tramway/multiselect" // importing Multiselect controller class
484
+ eagerLoadControllersFrom("controllers", application)
485
+
486
+ application.register('multiselect', Multiselect) // register Multiselect controller class as `multiselect` stimulus controller
487
+ ```
488
+
441
489
  ### Tailwind-styled pagination for Kaminari
442
490
 
443
491
  Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari).
@@ -480,6 +528,12 @@ user_2 = tramway_form User.first
480
528
  user_2.object #=> returns pure user object
481
529
  ```
482
530
 
531
+ ## Articles
532
+ * [Tramway on Rails](https://kalashnikovisme.medium.com/tramway-on-rails-32158c35ed68)
533
+ * [Delegating ActiveRecord methods to decorators in Rails](https://kalashnikovisme.medium.com/delegating-activerecord-methods-to-decorators-in-rails-4e4ec1c6b3a6)
534
+ * [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)
535
+ * [Decorating associations in Rails with Tramway](https://kalashnikovisme.medium.com/decorating-associations-in-rails-with-tramway-b46a28392f9e)
536
+
483
537
  ## Contributing
484
538
 
485
539
  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 submit(action, **options, &)
39
- render(Tailwinds::Form::SubmitButtonComponent.new(action, **options), &)
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,3 @@
1
+ #dropdown.absolute.shadow.top-100.bg-white.z-40.w-full.lef-0.rounded.max-h-select.overflow-y-auto{ data: { action: "click@window->multiselect#closeOnClickOutside" } }
2
+ .flex.flex-col.w-full
3
+ {{content}}
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tailwinds
4
+ module Form
5
+ module Multiselect
6
+ # Container for dropdown component
7
+ class DropdownContainer < ViewComponent::Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,11 @@
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 < ViewComponent::Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,2 @@
1
+ .flex-1
2
+ = @input.call(@attribute, @options.merge(placeholder: "{{placeholder}}", class: "bg-transparent p-1 px-2 appearance-none outline-none h-full w-full text-gray-800 hidden", data: { 'multiselect-target' => 'hiddenInput' }))
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tailwinds
4
+ module Form
5
+ module Multiselect
6
+ # Tailwind-styled multi-select field
7
+ class SelectedItemTemplate < ViewComponent::Base
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
+ def controller
23
+ :multiselect
24
+ end
25
+
26
+ private
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tailwinds
4
4
  module Form
5
- # Tailwind-styled text field
5
+ # Tailwind-styled select field
6
6
  class SelectComponent < TailwindComponent
7
7
  option :collection
8
8
  end
data/config/routes.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Lint/EmptyBlock
3
4
  Tramway::Engine.routes.draw do
4
5
  end
6
+ # rubocop:enable Lint/EmptyBlock
@@ -8,7 +8,7 @@ module Rules
8
8
  options.reduce({}) do |hash, (key, value)|
9
9
  case key
10
10
  when :method, :confirm
11
- hash.deep_merge data: { "turbo_#{key}".to_sym => value }
11
+ hash.deep_merge data: { "turbo_#{key}": value }
12
12
  else
13
13
  hash.merge key => value
14
14
  end
@@ -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
- OpenStruct.new index: Rails.application.routes.url_helpers.public_send(route_helper_method)
19
+ RouteStruct.new(Rails.application.routes.url_helpers.public_send(route_helper_method))
14
20
  end
15
21
 
16
22
  def human_name
17
- options = if model_class.present?
18
- model_name = model_class.model_name.human
19
- { single: model_name, plural: model_name.pluralize }
20
- else
21
- { single: name.capitalize, plural: name.pluralize.capitalize }
22
- end
23
- OpenStruct.new(**options)
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
@@ -14,6 +14,10 @@ module Tramway
14
14
  configure_pagination if Tramway.config.pagination[:enabled]
15
15
  end
16
16
 
17
+ initializer 'tramway.assets.precompile' do |app|
18
+ app.config.assets.precompile += %w[tramway/multiselect.js]
19
+ end
20
+
17
21
  private
18
22
 
19
23
  def load_navbar_helper
@@ -27,7 +27,7 @@ module Tramway
27
27
  navbar_items.each do |(key, value)|
28
28
  key_to_merge = case key
29
29
  when :left, :right
30
- "#{key}_items".to_sym
30
+ :"#{key}_items"
31
31
  else
32
32
  key
33
33
  end
@@ -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, *args, **options, &)
8
- form_for(object, *args, **options.merge(builder: Tailwinds::Form::Builder), &)
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
@@ -33,13 +33,13 @@ module Tramway
33
33
  reset_filling
34
34
  end
35
35
 
36
- def item(text_or_url, url = nil, **options, &block)
37
- raise 'You cannot provide an argument and a code block at the same time' if provided_url_and_block?(url, &block)
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, **options)
40
+ render_ignoring_block(text_or_url, url, **)
41
41
  else
42
- render_using_block(text_or_url, **options, &block)
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, &block)
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), &block)
86
+ context.render(Tailwinds::Nav::Item::ButtonComponent.new(method:, **options), &)
87
87
  else
88
- context.render(Tailwinds::Nav::Item::LinkComponent.new(method:, **options), &block)
88
+ context.render(Tailwinds::Nav::Item::LinkComponent.new(method:, **options), &)
89
89
  end
90
90
  end
91
91
  end
@@ -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(*args, &)
9
- ActionController::Base.render(*args, &)
8
+ def render(*, &)
9
+ ActionController::Base.render(*, &)
10
10
  end
11
11
  end
12
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '0.4.8'
4
+ VERSION = '0.4.9.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: 0.4.8
4
+ version: 0.4.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - kalashnikovisme
@@ -9,10 +9,10 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-08-08 00:00:00.000000000 Z
12
+ date: 2024-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: dry-struct
15
+ name: dry-initializer
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - ">="
@@ -26,7 +26,7 @@ dependencies:
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
- name: haml-rails
29
+ name: dry-struct
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - ">="
@@ -40,41 +40,47 @@ dependencies:
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
42
  - !ruby/object:Gem::Dependency
43
- name: rails
43
+ name: haml-rails
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '7'
48
+ version: '0'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: '7'
55
+ version: '0'
56
56
  - !ruby/object:Gem::Dependency
57
- name: view_component
57
+ name: rails
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: '0'
62
+ version: '7'
63
+ - - "<"
64
+ - !ruby/object:Gem::Version
65
+ version: '9'
63
66
  type: :runtime
64
67
  prerelease: false
65
68
  version_requirements: !ruby/object:Gem::Requirement
66
69
  requirements:
67
70
  - - ">="
68
71
  - !ruby/object:Gem::Version
69
- version: '0'
72
+ version: '7'
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: '9'
70
76
  - !ruby/object:Gem::Dependency
71
- name: rspec-rails
77
+ name: view_component
72
78
  requirement: !ruby/object:Gem::Requirement
73
79
  requirements:
74
80
  - - ">="
75
81
  - !ruby/object:Gem::Version
76
82
  version: '0'
77
- type: :development
83
+ type: :runtime
78
84
  prerelease: false
79
85
  version_requirements: !ruby/object:Gem::Requirement
80
86
  requirements:
@@ -91,11 +97,22 @@ files:
91
97
  - MIT-LICENSE
92
98
  - README.md
93
99
  - Rakefile
100
+ - app/assets/javascripts/tramway/multiselect_controller.js
94
101
  - app/components/tailwind_component.html.haml
95
102
  - app/components/tailwind_component.rb
96
103
  - app/components/tailwinds/form/builder.rb
97
104
  - app/components/tailwinds/form/file_field_component.html.haml
98
105
  - app/components/tailwinds/form/file_field_component.rb
106
+ - app/components/tailwinds/form/multiselect/dropdown_container.html.haml
107
+ - app/components/tailwinds/form/multiselect/dropdown_container.rb
108
+ - app/components/tailwinds/form/multiselect/item_container.html.haml
109
+ - app/components/tailwinds/form/multiselect/item_container.rb
110
+ - app/components/tailwinds/form/multiselect/select_as_input.html.haml
111
+ - app/components/tailwinds/form/multiselect/select_as_input.rb
112
+ - app/components/tailwinds/form/multiselect/selected_item_template.html.haml
113
+ - app/components/tailwinds/form/multiselect/selected_item_template.rb
114
+ - app/components/tailwinds/form/multiselect_component.html.haml
115
+ - app/components/tailwinds/form/multiselect_component.rb
99
116
  - app/components/tailwinds/form/select_component.html.haml
100
117
  - app/components/tailwinds/form/select_component.rb
101
118
  - app/components/tailwinds/form/submit_button_component.html.haml