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.
- checksums.yaml +4 -4
- data/README.md +22 -11
- data/app/assets/javascripts/tramway/{multiselect_controller.js → tramway-select_controller.js} +44 -6
- data/app/components/tailwind_component.rb +3 -3
- data/app/components/tramway/form/builder.rb +33 -21
- data/app/components/tramway/form/tramway_select/autocomplete_input_component.html.haml +1 -0
- data/app/components/tramway/form/tramway_select/autocomplete_input_component.rb +10 -0
- data/app/components/tramway/form/{multiselect → tramway_select}/caret_component.rb +1 -1
- data/app/components/tramway/form/{multiselect → tramway_select}/dropdown_container_component.html.haml +1 -1
- data/app/components/tramway/form/{multiselect → tramway_select}/dropdown_container_component.rb +1 -1
- data/app/components/tramway/form/tramway_select/item_container_component.html.haml +2 -0
- data/app/components/tramway/form/{multiselect → tramway_select}/item_container_component.rb +1 -1
- data/app/components/tramway/form/{multiselect → tramway_select}/select_as_input_component.html.haml +1 -1
- data/app/components/tramway/form/{multiselect → tramway_select}/select_as_input_component.rb +1 -1
- data/app/components/tramway/form/{multiselect → tramway_select}/selected_item_template_component.html.haml +1 -1
- data/app/components/tramway/form/tramway_select/selected_item_template_component.rb +29 -0
- data/app/components/tramway/form/tramway_select_component.html.haml +13 -0
- data/app/components/tramway/form/{multiselect_component.rb → tramway_select_component.rb} +38 -15
- data/config/tailwind.config.js +4 -2
- data/docs/AGENTS.md +15 -1
- data/lib/generators/tramway/install/install_generator.rb +4 -4
- data/lib/tramway/engine.rb +1 -1
- data/lib/tramway/utils/field.rb +2 -2
- data/lib/tramway/version.rb +1 -1
- metadata +16 -14
- data/app/components/tramway/form/multiselect/item_container_component.html.haml +0 -2
- data/app/components/tramway/form/multiselect/selected_item_template_component.rb +0 -26
- data/app/components/tramway/form/multiselect_component.html.haml +0 -13
- /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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56f897f311d05eaa3d077f0f02a2b28d116bd2bbdfb7f460b05ab3d481b5b57e
|
|
4
|
+
data.tar.gz: 4dec440f2451c89ff6b1bd10c93d79ff9a12a99c528129bb19b267bd7f59b95b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
#####
|
|
1192
|
+
##### Tramway Select
|
|
1182
1193
|
|
|
1183
|
-
In case you want to use tailwind-styled
|
|
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.
|
|
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
|
|
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/
|
|
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 {
|
|
1216
|
+
import { TramwaySelect } from "@tramway/tramway-select" // importing TramwaySelect controller class
|
|
1206
1217
|
eagerLoadControllersFrom("controllers", application)
|
|
1207
1218
|
|
|
1208
|
-
application.register('
|
|
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
|
|
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.
|
|
1226
|
+
<%= f.tramway_select :role, data: { action: 'change->user-form#updateForm' } %>
|
|
1216
1227
|
<% end %>
|
|
1217
1228
|
```
|
|
1218
1229
|
|
data/app/assets/javascripts/tramway/{multiselect_controller.js → tramway-select_controller.js}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
|
-
export default class
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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" } }
|
data/app/components/tramway/form/{multiselect → tramway_select}/select_as_input_component.html.haml
RENAMED
|
@@ -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: { '
|
|
3
|
+
= @input.call(@attribute, @options.merge(placeholder: "{{placeholder}}", class: classes, data: { 'tramway-select-target' => 'hiddenInput' }))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
%div{ class: selected_item_classes, data: { action: "click->
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
+
# rubocop:disable Metrics/AbcSize
|
|
19
|
+
def tramway_select_hash
|
|
20
|
+
default = {
|
|
18
21
|
controller:,
|
|
19
22
|
selected_item_template:,
|
|
20
|
-
|
|
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 = [
|
|
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
|
-
'
|
|
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(:
|
|
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->
|
|
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
|
|
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/
|
|
99
|
+
'tramway/form/tramway_select/select_as_input',
|
|
77
100
|
options:,
|
|
78
101
|
attribute:,
|
|
79
102
|
input:,
|
|
80
|
-
size_class: size_class(:
|
|
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
|
|
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/
|
|
126
|
+
component('tramway/form/tramway_select/dropdown_container', size:)
|
|
104
127
|
end
|
|
105
128
|
|
|
106
129
|
def item_container
|
|
107
|
-
component('tramway/form/
|
|
130
|
+
component('tramway/form/tramway_select/item_container', size:)
|
|
108
131
|
end
|
|
109
132
|
end
|
|
110
133
|
end
|
data/config/tailwind.config.js
CHANGED
|
@@ -334,7 +334,7 @@ module.exports = {
|
|
|
334
334
|
'text-red-800',
|
|
335
335
|
'space-x-2',
|
|
336
336
|
|
|
337
|
-
// ===
|
|
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
|
-
// ===
|
|
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
|
-
- `
|
|
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
|
|
56
|
-
'pin "@tramway/
|
|
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?(
|
|
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("#{
|
|
249
|
+
file.write("#{importmap_tramway_select_pin}\n")
|
|
250
250
|
end
|
|
251
251
|
end
|
|
252
252
|
end
|
data/lib/tramway/engine.rb
CHANGED
data/lib/tramway/utils/field.rb
CHANGED
|
@@ -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, :
|
|
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, :
|
|
26
|
+
when :text_area, :select, :tramway_select, :check_box
|
|
27
27
|
field_data
|
|
28
28
|
when :checkbox
|
|
29
29
|
:check_box
|
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: 3.0.
|
|
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,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
|
|
File without changes
|