tramway 0.4.8 → 0.4.9.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 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