tramway 3.0.0.1 → 3.0.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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -11
  3. data/app/assets/javascripts/tramway/{multiselect_controller.js → tramway-select_controller.js} +44 -6
  4. data/app/components/tailwind_component.rb +3 -3
  5. data/app/components/tramway/form/builder.rb +33 -21
  6. data/app/components/tramway/form/tramway_select/autocomplete_input_component.html.haml +1 -0
  7. data/app/components/tramway/form/tramway_select/autocomplete_input_component.rb +10 -0
  8. data/app/components/tramway/form/{multiselect → tramway_select}/caret_component.rb +1 -1
  9. data/app/components/tramway/form/{multiselect → tramway_select}/dropdown_container_component.html.haml +1 -1
  10. data/app/components/tramway/form/{multiselect → tramway_select}/dropdown_container_component.rb +1 -1
  11. data/app/components/tramway/form/tramway_select/item_container_component.html.haml +2 -0
  12. data/app/components/tramway/form/{multiselect → tramway_select}/item_container_component.rb +1 -1
  13. data/app/components/tramway/form/{multiselect → tramway_select}/select_as_input_component.html.haml +1 -1
  14. data/app/components/tramway/form/{multiselect → tramway_select}/select_as_input_component.rb +1 -1
  15. data/app/components/tramway/form/{multiselect → tramway_select}/selected_item_template_component.html.haml +1 -1
  16. data/app/components/tramway/form/tramway_select/selected_item_template_component.rb +29 -0
  17. data/app/components/tramway/form/tramway_select_component.html.haml +13 -0
  18. data/app/components/tramway/form/{multiselect_component.rb → tramway_select_component.rb} +38 -15
  19. data/config/tailwind.config.js +4 -2
  20. data/docs/AGENTS.md +15 -1
  21. data/lib/generators/tramway/install/install_generator.rb +4 -4
  22. data/lib/tramway/engine.rb +1 -1
  23. data/lib/tramway/utils/field.rb +2 -2
  24. data/lib/tramway/version.rb +1 -1
  25. metadata +16 -14
  26. data/app/components/tramway/form/multiselect/item_container_component.html.haml +0 -2
  27. data/app/components/tramway/form/multiselect/selected_item_template_component.rb +0 -26
  28. data/app/components/tramway/form/multiselect_component.html.haml +0 -13
  29. /data/app/components/tramway/form/{multiselect → tramway_select}/caret_component.html.haml +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddcb9362d874433878e243879b046128dab5bceb12dab30d62ae2f8f39ab3aa5
4
- data.tar.gz: 1452740d4bd20c3fff0741dac5aa99b21534b27b1493ecb1539b7e4ec84b38f6
3
+ metadata.gz: 56f897f311d05eaa3d077f0f02a2b28d116bd2bbdfb7f460b05ab3d481b5b57e
4
+ data.tar.gz: 4dec440f2451c89ff6b1bd10c93d79ff9a12a99c528129bb19b267bd7f59b95b
5
5
  SHA512:
6
- metadata.gz: 34233f340fce1fce40ccc9baa2848541fac897ecdfa9c6bfd1f28552dac5b08be19c4a6bb5f810034bfdeda1adf00494c9cb2f5f6e77cb78bbbcb1403e351cda
7
- data.tar.gz: 417cdfd0e3cfbe1e2bc9f47a8563e0bd4e56cfa20a250a4bdd8abaac535e1d3edc007ca2d1b59e743da226d7b314392314055589ec35e68a4a115e2b9e980a74
6
+ metadata.gz: 732effab54397590ae4320802c803c70fe648765ff200ce0e994e396c13eabed2501361c645a7e2a55019a81cdba4987e26615c7c71e5aa05ab1add573188d06
7
+ data.tar.gz: 77a23247e58a804ff62e2c4a1071db86aae1d746832268d39da9e70fcab5e276264b16d60125da72be38ff40e4f2a01c182cd453b56c66eb02766fe649f65f7c
data/README.md CHANGED
@@ -1105,7 +1105,7 @@ Tramway provides `tramway_form_for` helper that renders Tailwind-styled forms by
1105
1105
  <%= f.select :role, [:admin, :user] %>
1106
1106
  <%= f.date_field :birth_date %>
1107
1107
  <%= f.datetime_field :confirmed_at %>
1108
- <%= f.multiselect :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']] %>
1108
+ <%= f.tramway_select :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']] %>
1109
1109
  <%= f.file_field :file %>
1110
1110
  <%= f.submit 'Create User' %>
1111
1111
  <% end %>
@@ -1144,9 +1144,20 @@ Available form helpers:
1144
1144
  * date_field
1145
1145
  * datetime_field
1146
1146
  * time_field
1147
- * multiselect ([Stimulus-based](https://github.com/Purple-Magic/tramway#stimulus-based-inputs))
1147
+ * tramway_select ([Stimulus-based](https://github.com/Purple-Magic/tramway#stimulus-based-inputs))
1148
1148
  * submit
1149
1149
 
1150
+ Autocomplete select example:
1151
+
1152
+ ```erb
1153
+ <%= tramway_form_for @user do |f| %>
1154
+ <%= f.select :role, [:admin, :user], autocomplete: true %>
1155
+ <% end %>
1156
+ ```
1157
+
1158
+ `autocomplete: true` renders an autocomplete-enabled select. It cannot be used together with `multiselect: true` in the
1159
+ same select field.
1160
+
1150
1161
  **Examples**
1151
1162
 
1152
1163
  1. Sign In Form for `devise` authentication
@@ -1178,41 +1189,41 @@ Available form helpers:
1178
1189
 
1179
1190
  `tramway_form_for` provides Tailwind-styled Stimulus-based custom inputs.
1180
1191
 
1181
- ##### Multiselect
1192
+ ##### Tramway Select
1182
1193
 
1183
- In case you want to use tailwind-styled multiselect this way
1194
+ In case you want to use the tailwind-styled Tramway select this way
1184
1195
 
1185
1196
  ```erb
1186
1197
  <%= tramway_form_for @user do |f| %>
1187
- <%= f.multiselect :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']] %>
1198
+ <%= f.tramway_select :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']] %>
1188
1199
  <%# ... %>
1189
1200
  <% end %>
1190
1201
  ```
1191
1202
 
1192
- you should add Tramway Multiselect Stimulus controller to your application.
1203
+ you should add the Tramway Select Stimulus controller to your application.
1193
1204
 
1194
1205
  Example for [importmap-rails](https://github.com/rails/importmap-rails) config
1195
1206
 
1196
1207
  *config/importmap.rb*
1197
1208
  ```ruby
1198
- pin '@tramway/multiselect', to: 'tramway/multiselect_controller.js'
1209
+ pin '@tramway/tramway-select', to: 'tramway/tramway-select_controller.js'
1199
1210
  ```
1200
1211
 
1201
1212
  *app/javascript/controllers/index.js*
1202
1213
  ```js
1203
1214
  import { application } from "controllers/application"
1204
1215
  import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
1205
- import { Multiselect } from "@tramway/multiselect" // importing Multiselect controller class
1216
+ import { TramwaySelect } from "@tramway/tramway-select" // importing TramwaySelect controller class
1206
1217
  eagerLoadControllersFrom("controllers", application)
1207
1218
 
1208
- application.register('multiselect', Multiselect) // register Multiselect controller class as `multiselect` stimulus controller
1219
+ application.register('tramway-select', TramwaySelect) // register TramwaySelect controller class as `tramway-select` stimulus controller
1209
1220
  ```
1210
1221
 
1211
- In case you need to use Stimulus `change` action with Tramway Multiselect
1222
+ In case you need to use Stimulus `change` action with Tramway Select
1212
1223
 
1213
1224
  ```erb
1214
1225
  <%= tramway_form_for @user do |f| %>
1215
- <%= f.multiselect :role, data: { action: 'change->user-form#updateForm' } %>
1226
+ <%= f.tramway_select :role, data: { action: 'change->user-form#updateForm' } %>
1216
1227
  <% end %>
1217
1228
  ```
1218
1229
 
@@ -1,6 +1,6 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
2
 
3
- export default class Multiselect extends Controller {
3
+ export default class TramwaySelect extends Controller {
4
4
  static targets = ["dropdown", "showSelectedArea", "hiddenInput", "caretDown", "caretUp"]
5
5
 
6
6
  static values = {
@@ -13,11 +13,15 @@ export default class Multiselect extends Controller {
13
13
  placeholder: String,
14
14
  selectAsInput: String,
15
15
  value: Array,
16
- onChange: String
16
+ onChange: String,
17
+ multiple: Boolean,
18
+ autocomplete: Boolean,
19
+ autocompleteInput: String
17
20
  }
18
21
 
19
22
  connect() {
20
23
  this.dropdownState = 'closed';
24
+
21
25
  this.items = JSON.parse(this.element.dataset.items).map((item, index) => {
22
26
  return {
23
27
  index,
@@ -40,9 +44,15 @@ export default class Multiselect extends Controller {
40
44
  }
41
45
 
42
46
  renderSelectedItems() {
43
- const allItems = this.fillTemplate(this.element.dataset.selectedItemTemplate, this.selectedItems);
47
+ const allItems = this.fillTemplate(this.element.dataset.selectedItemTemplate, this.selectedItems)
48
+
49
+ let content = allItems;
50
+
51
+ if (this.autocomplete() && this.selectedItems.length === 0) {
52
+ content += this.element.dataset.autocompleteInput;
53
+ }
44
54
 
45
- this.showSelectedAreaTarget.innerHTML = allItems;
55
+ this.showSelectedAreaTarget.innerHTML = content;
46
56
  this.showSelectedAreaTarget.insertAdjacentHTML("beforeEnd", this.input());
47
57
  this.updateInputOptions();
48
58
  }
@@ -90,6 +100,7 @@ export default class Multiselect extends Controller {
90
100
 
91
101
  closeDropdown() {
92
102
  this.dropdownState = 'closed';
103
+
93
104
  if (this.dropdown()) {
94
105
  this.dropdown().remove();
95
106
  }
@@ -126,6 +137,12 @@ export default class Multiselect extends Controller {
126
137
  const itemIndex = this.items.findIndex(x => x.value === currentTarget.dataset.value);
127
138
  const itemSelectedIndex = this.selectedItems.findIndex(x => x.value === currentTarget.dataset.value);
128
139
 
140
+ if (!this.multiple()) {
141
+ this.selectedItems = [];
142
+ this.items.forEach(item => item.selected = false);
143
+ this.closeDropdown()
144
+ }
145
+
129
146
  if (itemSelectedIndex !== -1) {
130
147
  this.selectedItems = this.selectedItems.filter((_, index) => index !== itemSelectedIndex);
131
148
  this.items[itemIndex].selected = false;
@@ -135,7 +152,10 @@ export default class Multiselect extends Controller {
135
152
  }
136
153
 
137
154
  this.renderSelectedItems();
138
- this.rerenderItems();
155
+
156
+ if (this.multiple()) {
157
+ this.rerenderItems();
158
+ }
139
159
  }
140
160
 
141
161
  input() {
@@ -155,6 +175,24 @@ export default class Multiselect extends Controller {
155
175
 
156
176
  this.hiddenInputTarget.value = this.selectedItems.map(item => item.value);
157
177
  }
178
+
179
+ multiple() {
180
+ return this.element.dataset.multiple == 'true';
181
+ }
182
+
183
+ autocomplete() {
184
+ return this.element.dataset.autocomplete == 'true';
185
+ }
186
+
187
+ search(event) {
188
+ const searchTerm = event.target.value.toLowerCase();
189
+ const filteredItems = this.items.filter(item => item.text.toLowerCase().includes(searchTerm) && !item.selected);
190
+ const dropdown = this.dropdown();
191
+
192
+ if (dropdown) {
193
+ dropdown.innerHTML = this.fillTemplate(this.element.dataset.itemContainer, filteredItems);
194
+ }
195
+ }
158
196
  }
159
197
 
160
- export { Multiselect }
198
+ export { TramwaySelect }
@@ -18,7 +18,7 @@ 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 h-10',
21
+ tramway_select_input: 'text-sm px-2 py-1 h-10',
22
22
  checkbox_input: 'h-4 w-4'
23
23
  },
24
24
  medium: {
@@ -26,7 +26,7 @@ class TailwindComponent < Tramway::BaseComponent
26
26
  select_input: 'text-base px-3 py-2',
27
27
  file_button: 'text-base px-4 py-2',
28
28
  submit_button: 'text-base px-4 py-2',
29
- multiselect_input: 'text-base px-2 py-1 h-12',
29
+ tramway_select_input: 'text-base px-2 py-1 h-12',
30
30
  checkbox_input: 'h-5 w-5'
31
31
  },
32
32
  large: {
@@ -34,7 +34,7 @@ class TailwindComponent < Tramway::BaseComponent
34
34
  select_input: 'text-xl px-4 py-3',
35
35
  file_button: 'text-xl px-5 py-3',
36
36
  submit_button: 'text-xl px-5 py-3',
37
- multiselect_input: 'text-xl px-3 py-2 h-15',
37
+ tramway_select_input: 'text-xl px-3 py-2 h-15',
38
38
  checkbox_input: 'h-6 w-6'
39
39
  }
40
40
  }.freeze
@@ -83,25 +83,11 @@ module Tramway
83
83
  end
84
84
 
85
85
  def select(attribute, collection, **options, &)
86
- sanitized_options = sanitize_options(options)
87
-
88
- render(Tramway::Form::SelectComponent.new(
89
- input: input(:select),
90
- value: sanitized_options[:selected] || object.public_send(attribute),
91
- collection: explicitly_add_blank_option(collection, sanitized_options),
92
- **default_options(attribute, sanitized_options)
93
- ), &)
94
- end
95
-
96
- def multiselect(attribute, collection, **options, &)
97
- sanitized_options = sanitize_options(options)
98
-
99
- render(Tramway::Form::MultiselectComponent.new(
100
- input: input(:text_field),
101
- value: sanitized_options[:value] || sanitized_options[:selected] || object.public_send(attribute),
102
- collection:,
103
- **default_options(attribute, sanitized_options)
104
- ), &)
86
+ if options[:multiple] || options[:autocomplete]
87
+ tramway_select(attribute, collection, **options, &)
88
+ else
89
+ default_select(attribute, collection, **options, &)
90
+ end
105
91
  end
106
92
 
107
93
  def submit(action, **options, &)
@@ -122,6 +108,30 @@ module Tramway
122
108
 
123
109
  attr_reader :form_size
124
110
 
111
+ def default_select(attribute, collection, **options, &)
112
+ sanitized_options = sanitize_options(options)
113
+
114
+ render(Tramway::Form::SelectComponent.new(
115
+ input: input(:select),
116
+ value: sanitized_options[:selected] || object.public_send(attribute),
117
+ collection: explicitly_add_blank_option(collection, sanitized_options),
118
+ **default_options(attribute, sanitized_options)
119
+ ), &)
120
+ end
121
+
122
+ def tramway_select(attribute, collection, **options, &)
123
+ sanitized_options = sanitize_options(options)
124
+
125
+ render(Tramway::Form::TramwaySelectComponent.new(
126
+ input: input(:text_field),
127
+ value: sanitized_options[:value] || sanitized_options[:selected] || object.public_send(attribute),
128
+ collection:,
129
+ multiple: options[:multiple],
130
+ autocomplete: options[:autocomplete],
131
+ **default_options(attribute, sanitized_options)
132
+ ), &)
133
+ end
134
+
125
135
  def input(method_name)
126
136
  unbound_method = self.class.superclass.instance_method(method_name)
127
137
  unbound_method.bind(self)
@@ -157,8 +167,10 @@ module Tramway
157
167
 
158
168
  def sanitize_options(options)
159
169
  options.dup.tap do |opts|
160
- opts.delete(:size)
161
- opts.delete('size')
170
+ %i[size multiple autocomplete].each do |key|
171
+ opts.delete(key)
172
+ opts.delete(key.to_s)
173
+ end
162
174
  end
163
175
  end
164
176
 
@@ -0,0 +1 @@
1
+ %input.focus:outline-none.focus:ring-0.w-full{ type: :text, data: { action: "input->tramway-select#search" } }
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Form
5
+ module TramwaySelect
6
+ class AutocompleteInputComponent < Tramway::BaseComponent
7
+ end
8
+ end
9
+ end
10
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tramway
4
4
  module Form
5
- module Multiselect
5
+ module TramwaySelect
6
6
  # Caret icon component
7
7
  class CaretComponent < Tramway::BaseComponent
8
8
  option :direction
@@ -1,3 +1,3 @@
1
- #dropdown{ class: dropdown_classes, data: { action: "click@window->multiselect#closeOnClickOutside" } }
1
+ #dropdown{ class: dropdown_classes, data: { action: "click@window->tramway-select#closeOnClickOutside" } }
2
2
  .flex.flex-col.w-full
3
3
  {{content}}
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tramway
4
4
  module Form
5
- module Multiselect
5
+ module TramwaySelect
6
6
  # Container for dropdown component
7
7
  class DropdownContainerComponent < Tramway::BaseComponent
8
8
  option :size
@@ -0,0 +1,2 @@
1
+ %div{ class: item_classes, data: { action: "click->tramway-select#toggleItem", text: "{{text}}", value: "{{value}}" } }
2
+ {{text}}
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tramway
4
4
  module Form
5
- module Multiselect
5
+ module TramwaySelect
6
6
  # Container for item in dropdown component
7
7
  class ItemContainerComponent < Tramway::BaseComponent
8
8
  option :size
@@ -1,3 +1,3 @@
1
1
  .flex-1
2
2
  - classes = "#{@size_class} #{base_classes}"
3
- = @input.call(@attribute, @options.merge(placeholder: "{{placeholder}}", class: classes, data: { 'multiselect-target' => 'hiddenInput' }))
3
+ = @input.call(@attribute, @options.merge(placeholder: "{{placeholder}}", class: classes, data: { 'tramway-select-target' => 'hiddenInput' }))
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Tramway
4
4
  module Form
5
- module Multiselect
5
+ module TramwaySelect
6
6
  # Renders input as select
7
7
  class SelectAsInputComponent < Tramway::BaseComponent
8
8
  option :options
@@ -1,4 +1,4 @@
1
- %div{ class: selected_item_classes, data: { action: "click->multiselect#toggleItem", text: "{{text}}", value: "{{value}}" } }
1
+ %div{ class: selected_item_classes, data: { action: "click->tramway-select#toggleItem", text: "{{text}}", value: "{{value}}" } }
2
2
  .font-normal.leading-none.max-w-full.flex-initial
3
3
  {{text}}
4
4
  .flex.flex-auto.flex-row-reverse
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Form
5
+ module TramwaySelect
6
+ # Tailwind-styled tramway select field
7
+ class SelectedItemTemplateComponent < Tramway::BaseComponent
8
+ option :size
9
+ option :multiple
10
+
11
+ SIZE_CLASSES = {
12
+ small: 'text-sm',
13
+ medium: 'text-base',
14
+ large: 'text-lg'
15
+ }.freeze
16
+
17
+ def selected_item_classes
18
+ classes = 'flex justify-center items-center font-medium py-1 px-2 rounded-xl ' \
19
+ 'text-white shadow-md hover:bg-gray-800 cursor-pointer ' \
20
+ 'space-x-1 selected-option ' + SIZE_CLASSES[size].to_s
21
+
22
+ classes += ' border border-gray-700' if multiple
23
+
24
+ theme_classes classic: classes
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ .relative{ class: default_container_classes }
2
+ - if @label
3
+ = component('tramway/form/label', for: @for) do
4
+ = @label
5
+ %div{ role: :combobox, data: tramway_select_hash, id: "#{@for}_tramway_select", class: tramway_select_classes }
6
+ - classes = "#{size_class(:tramway_select_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.w-full{ data: { "tramway-select-target" => "showSelectedArea" } }
9
+ .flex.flex-col.justify-center
10
+ .caret-down{ data: { "tramway-select-target" => "caretDown" } }
11
+ = component 'tramway/form/tramway_select/caret', size:, direction: :down
12
+ .caret-up.hidden{ data: { "tramway-select-target" => "caretUp" } }
13
+ = component 'tramway/form/tramway_select/caret', size:, direction: :up
@@ -3,8 +3,10 @@
3
3
  module Tramway
4
4
  module Form
5
5
  # Tailwind-styled multi-select field
6
- class MultiselectComponent < TailwindComponent
6
+ class TramwaySelectComponent < TailwindComponent
7
7
  option :collection
8
+ option :multiple, optional: true, default: -> { false }
9
+ option :autocomplete, optional: true, default: -> { false }
8
10
 
9
11
  def before_render
10
12
  @collection = collection.map do |(text, value)|
@@ -13,11 +15,12 @@ module Tramway
13
15
  end
14
16
 
15
17
  # rubocop:disable Metrics/MethodLength
16
- def multiselect_hash
17
- {
18
+ # rubocop:disable Metrics/AbcSize
19
+ def tramway_select_hash
20
+ default = {
18
21
  controller:,
19
22
  selected_item_template:,
20
- multiselect_selected_items_value:,
23
+ tramway_select_selected_items_value:,
21
24
  dropdown_container:,
22
25
  item_container:,
23
26
  items:,
@@ -25,13 +28,33 @@ module Tramway
25
28
  select_as_input:,
26
29
  placeholder:,
27
30
  value:,
28
- on_change:
31
+ on_change:,
32
+ multiple: multiple.to_s,
33
+ autocomplete: autocomplete.to_s
29
34
  }.transform_keys { |key| key.to_s.gsub('_', '-') }
35
+
36
+ default.merge!(autocomplete_input:) if autocomplete
37
+
38
+ default
30
39
  end
31
40
  # rubocop:enable Metrics/MethodLength
41
+ # rubocop:enable Metrics/AbcSize
42
+
43
+ def autocomplete_input
44
+ component 'tramway/form/tramway_select/autocomplete_input'
45
+ end
46
+
47
+ def tramway_select_classes
48
+ classes = ''
49
+
50
+ classes += 'select--multiple ' if multiple
51
+ classes += 'select--autocomplete ' if autocomplete
52
+
53
+ classes
54
+ end
32
55
 
33
56
  def controller
34
- controllers = [:multiselect]
57
+ controllers = ['tramway-select']
35
58
  controllers << external_action.split('->').last.split('#').first if external_action
36
59
  controllers += external_controllers
37
60
  controllers.join(' ')
@@ -39,7 +62,7 @@ module Tramway
39
62
 
40
63
  def dropdown_data
41
64
  (options[:data] || {}).merge(
42
- 'multiselect-target' => 'dropdown',
65
+ 'tramway-select-target' => 'dropdown',
43
66
  'dropdown-container' => dropdown_container,
44
67
  'item-container' => item_container
45
68
  )
@@ -50,13 +73,13 @@ module Tramway
50
73
  end
51
74
 
52
75
  def input_classes
53
- "#{size_class(:multiselect_input)} #{select_base_classes}"
76
+ "#{size_class(:tramway_select_input)} #{select_base_classes}"
54
77
  end
55
78
 
56
79
  private
57
80
 
58
81
  def action
59
- 'click->multiselect#toggleDropdown'
82
+ 'click->tramway-select#toggleDropdown'
60
83
  end
61
84
 
62
85
  def items
@@ -67,17 +90,17 @@ module Tramway
67
90
  options[:placeholder]
68
91
  end
69
92
 
70
- def multiselect_selected_items_value
93
+ def tramway_select_selected_items_value
71
94
  []
72
95
  end
73
96
 
74
97
  def select_as_input
75
98
  component(
76
- 'tramway/form/multiselect/select_as_input',
99
+ 'tramway/form/tramway_select/select_as_input',
77
100
  options:,
78
101
  attribute:,
79
102
  input:,
80
- size_class: size_class(:multiselect_input)
103
+ size_class: size_class(:tramway_select_input)
81
104
  )
82
105
  end
83
106
 
@@ -96,15 +119,15 @@ module Tramway
96
119
  end
97
120
 
98
121
  def selected_item_template
99
- component('tramway/form/multiselect/selected_item_template', size:)
122
+ component 'tramway/form/tramway_select/selected_item_template', size:, multiple:
100
123
  end
101
124
 
102
125
  def dropdown_container
103
- component('tramway/form/multiselect/dropdown_container', size:)
126
+ component('tramway/form/tramway_select/dropdown_container', size:)
104
127
  end
105
128
 
106
129
  def item_container
107
- component('tramway/form/multiselect/item_container', size:)
130
+ component('tramway/form/tramway_select/item_container', size:)
108
131
  end
109
132
  end
110
133
  end
@@ -334,7 +334,7 @@ module.exports = {
334
334
  'text-red-800',
335
335
  'space-x-2',
336
336
 
337
- // === Multiselect dropdown positioning ===
337
+ // === Tramway select dropdown positioning ===
338
338
  'absolute',
339
339
  'relative',
340
340
  'shadow',
@@ -345,7 +345,7 @@ module.exports = {
345
345
  'border-gray-600',
346
346
  'text-gray-100',
347
347
 
348
- // === Multiselect option styling ===
348
+ // === Tramway select option styling ===
349
349
  'border-b',
350
350
  'border',
351
351
  'border-l',
@@ -378,6 +378,8 @@ module.exports = {
378
378
  'py-1',
379
379
  'flex-nowrap',
380
380
  'overflow-x-auto',
381
+ 'focus:outline-none',
382
+ 'focus:ring-0',
381
383
 
382
384
  // === Flash message styles ===
383
385
  'fixed',
data/docs/AGENTS.md CHANGED
@@ -155,6 +155,9 @@ Use Tramway Button for buttons. Always add a color of the button via `color:` or
155
155
  ### Rule 7
156
156
  Use `tramway_form_for` instead `form_with`, `form_for`
157
157
 
158
+ `tramway_form_for` has an upgraded `select` helper. Use `autocomplete: true` when you need an autocomplete select instead
159
+ of the usual select element. Do not use `autocomplete: true` together with `multiselect: true` on the same field.
160
+
158
161
  Available `tramway_form_for` helpers:
159
162
  - `text_field`
160
163
  - `email_field`
@@ -167,7 +170,7 @@ Available `tramway_form_for` helpers:
167
170
  - `date_field`
168
171
  - `datetime_field`
169
172
  - `time_field`
170
- - `multiselect`
173
+ - `tramway_select`
171
174
  - `submit`
172
175
 
173
176
  ### Rule 8
@@ -595,6 +598,17 @@ end
595
598
  = f.submit 'Save'
596
599
  ```
597
600
 
601
+ Autocomplete select example:
602
+
603
+ ```ruby
604
+ = tramway_form_for @user do |f|
605
+ = f.select :role, [["Admin", "admin"], ["Manager", "manager"]], autocomplete: true
606
+ = f.submit 'Save'
607
+ ```
608
+
609
+ `autocomplete: true` renders an autocomplete select. `autocomplete: true` and `multiselect: true` cannot be used together
610
+ in one select field.
611
+
598
612
  `tramway_form_for` supports `horizontal: true` for horizontal form layout.
599
613
 
600
614
  ```ruby
@@ -52,8 +52,8 @@ module Tramway
52
52
  @importmap_path ||= File.join(destination_root, 'config/importmap.rb')
53
53
  end
54
54
 
55
- def importmap_multiselect_pin
56
- 'pin "@tramway/multiselect", to: "tramway/multiselect_controller.js"'
55
+ def importmap_tramway_select_pin
56
+ 'pin "@tramway/tramway-select", to: "tramway/tramway-select_controller.js"'
57
57
  end
58
58
 
59
59
  def agents_file_path
@@ -242,11 +242,11 @@ module Tramway
242
242
  return unless File.exist?(importmap_path)
243
243
 
244
244
  content = File.read(importmap_path)
245
- return if content.include?(importmap_multiselect_pin)
245
+ return if content.include?(importmap_tramway_select_pin)
246
246
 
247
247
  File.open(importmap_path, 'a') do |file|
248
248
  file.write("\n") unless content.empty? || content.end_with?("\n")
249
- file.write("#{importmap_multiselect_pin}\n")
249
+ file.write("#{importmap_tramway_select_pin}\n")
250
250
  end
251
251
  end
252
252
  end
@@ -17,7 +17,7 @@ module Tramway
17
17
  end
18
18
 
19
19
  initializer 'tramway.assets.precompile' do |app|
20
- app.config.assets.precompile += %w[tramway/multiselect.js]
20
+ app.config.assets.precompile += %w[tramway/tramway-select_controller.js]
21
21
  end
22
22
 
23
23
  private
@@ -10,7 +10,7 @@ module Tramway
10
10
  input_options = field_options(field_data).merge(options.compact)
11
11
 
12
12
  case input_type.to_sym
13
- when :select, :multiselect
13
+ when :select, :tramway_select
14
14
  collection = input_options.delete(:collection)
15
15
 
16
16
  public_send(input_name, attribute, collection, **input_options, &)
@@ -23,7 +23,7 @@ module Tramway
23
23
 
24
24
  def field_name(field_data)
25
25
  case field_data.to_sym
26
- when :text_area, :select, :multiselect, :check_box
26
+ when :text_area, :select, :tramway_select, :check_box
27
27
  field_data
28
28
  when :checkbox
29
29
  :check_box
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '3.0.0.1'
4
+ VERSION = '3.0.1'
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: 3.0.0.1
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - kalashnikovisme
@@ -138,8 +138,8 @@ files:
138
138
  - MIT-LICENSE
139
139
  - README.md
140
140
  - Rakefile
141
- - app/assets/javascripts/tramway/multiselect_controller.js
142
141
  - app/assets/javascripts/tramway/table_row_preview_controller.js
142
+ - app/assets/javascripts/tramway/tramway-select_controller.js
143
143
  - app/components/tailwind_component.html.haml
144
144
  - app/components/tailwind_component.rb
145
145
  - app/components/tramway/actions_buttons_container_component.html.haml
@@ -182,18 +182,6 @@ files:
182
182
  - app/components/tramway/form/file_field_component.rb
183
183
  - app/components/tramway/form/label_component.html.haml
184
184
  - app/components/tramway/form/label_component.rb
185
- - app/components/tramway/form/multiselect/caret_component.html.haml
186
- - app/components/tramway/form/multiselect/caret_component.rb
187
- - app/components/tramway/form/multiselect/dropdown_container_component.html.haml
188
- - app/components/tramway/form/multiselect/dropdown_container_component.rb
189
- - app/components/tramway/form/multiselect/item_container_component.html.haml
190
- - app/components/tramway/form/multiselect/item_container_component.rb
191
- - app/components/tramway/form/multiselect/select_as_input_component.html.haml
192
- - app/components/tramway/form/multiselect/select_as_input_component.rb
193
- - app/components/tramway/form/multiselect/selected_item_template_component.html.haml
194
- - app/components/tramway/form/multiselect/selected_item_template_component.rb
195
- - app/components/tramway/form/multiselect_component.html.haml
196
- - app/components/tramway/form/multiselect_component.rb
197
185
  - app/components/tramway/form/number_field_component.html.haml
198
186
  - app/components/tramway/form/number_field_component.rb
199
187
  - app/components/tramway/form/select_component.html.haml
@@ -204,6 +192,20 @@ files:
204
192
  - app/components/tramway/form/text_field_component.rb
205
193
  - app/components/tramway/form/time_field_component.html.haml
206
194
  - app/components/tramway/form/time_field_component.rb
195
+ - app/components/tramway/form/tramway_select/autocomplete_input_component.html.haml
196
+ - app/components/tramway/form/tramway_select/autocomplete_input_component.rb
197
+ - app/components/tramway/form/tramway_select/caret_component.html.haml
198
+ - app/components/tramway/form/tramway_select/caret_component.rb
199
+ - app/components/tramway/form/tramway_select/dropdown_container_component.html.haml
200
+ - app/components/tramway/form/tramway_select/dropdown_container_component.rb
201
+ - app/components/tramway/form/tramway_select/item_container_component.html.haml
202
+ - app/components/tramway/form/tramway_select/item_container_component.rb
203
+ - app/components/tramway/form/tramway_select/select_as_input_component.html.haml
204
+ - app/components/tramway/form/tramway_select/select_as_input_component.rb
205
+ - app/components/tramway/form/tramway_select/selected_item_template_component.html.haml
206
+ - app/components/tramway/form/tramway_select/selected_item_template_component.rb
207
+ - app/components/tramway/form/tramway_select_component.html.haml
208
+ - app/components/tramway/form/tramway_select_component.rb
207
209
  - app/components/tramway/native_text_component.html.haml
208
210
  - app/components/tramway/native_text_component.rb
209
211
  - app/components/tramway/nav/item/button_component.html.haml
@@ -1,2 +0,0 @@
1
- %div{ class: item_classes, data: { action: "click->multiselect#toggleItem", text: "{{text}}", value: "{{value}}" } }
2
- {{text}}
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tramway
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,13 +0,0 @@
1
- .relative{ class: default_container_classes }
2
- - if @label
3
- = component('tramway/form/label', for: @for) do
4
- = @label
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 'tramway/form/multiselect/caret', size: size, direction: :down
12
- .caret-up.hidden{ data: { "multiselect-target" => "caretUp" } }
13
- = component 'tramway/form/multiselect/caret', size: size, direction: :up