tramway 0.4.9.3 → 0.5
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 +1 -3
- data/app/assets/javascripts/tramway/multiselect_controller.js +16 -16
- data/app/assets/javascripts/tramway/table_row_preview_controller.js +83 -0
- data/app/components/tailwind_component.rb +1 -3
- data/app/components/tailwinds/form/label_component.html.haml +2 -0
- data/app/components/tailwinds/form/label_component.rb +10 -0
- data/app/components/tailwinds/form/multiselect_component.html.haml +2 -2
- data/app/components/tailwinds/form/select_component.html.haml +1 -1
- data/app/components/tailwinds/form/text_field_component.html.haml +1 -1
- data/app/components/tailwinds/nav/item/button_component.rb +1 -1
- data/app/components/tailwinds/nav/item/link_component.rb +1 -1
- data/app/components/tailwinds/navbar_component.html.haml +34 -12
- data/app/components/tailwinds/pagination/base.rb +22 -0
- data/app/components/tailwinds/pagination/first_page_component.html.haml +3 -0
- data/app/components/tailwinds/pagination/first_page_component.rb +9 -0
- data/app/components/tailwinds/pagination/gap_component.html.haml +2 -0
- data/app/components/tailwinds/pagination/gap_component.rb +9 -0
- data/app/components/tailwinds/pagination/last_page_component.html.haml +3 -0
- data/app/components/tailwinds/pagination/last_page_component.rb +9 -0
- data/app/components/tailwinds/pagination/next_page_component.html.haml +3 -0
- data/app/components/tailwinds/pagination/next_page_component.rb +9 -0
- data/app/components/tailwinds/pagination/page_component.html.haml +5 -0
- data/app/components/tailwinds/pagination/page_component.rb +10 -0
- data/app/components/tailwinds/pagination/prev_page_component.html.haml +3 -0
- data/app/components/tailwinds/pagination/prev_page_component.rb +9 -0
- data/app/components/tailwinds/table/cell_component.html.haml +2 -0
- data/app/components/tailwinds/table/cell_component.rb +9 -0
- data/app/components/tailwinds/table/header_component.html.haml +6 -0
- data/app/components/tailwinds/table/header_component.rb +10 -0
- data/app/components/tailwinds/table/row/preview_component.html.haml +18 -0
- data/app/components/tailwinds/table/row/preview_component.rb +11 -0
- data/app/components/tailwinds/table/row_component.html.haml +24 -0
- data/app/components/tailwinds/table/row_component.rb +25 -0
- data/app/components/tailwinds/table_component.html.haml +4 -0
- data/app/components/tailwinds/table_component.rb +7 -0
- data/app/components/tramway/component/base.rb +11 -0
- data/app/controllers/tramway/entities_controller.rb +19 -0
- data/app/helpers/tramway/application_helper.rb +15 -0
- data/app/views/kaminari/_first_page.html.haml +1 -9
- data/app/views/kaminari/_gap.html.haml +1 -8
- data/app/views/kaminari/_last_page.html.haml +1 -9
- data/app/views/kaminari/_next_page.html.haml +1 -9
- data/app/views/kaminari/_page.html.haml +1 -14
- data/app/views/kaminari/_prev_page.html.haml +1 -9
- data/app/views/tramway/entities/_entity.html.haml +2 -0
- data/app/views/tramway/entities/index.html.haml +22 -0
- data/config/routes.rb +3 -2
- data/lib/tramway/base_decorator.rb +6 -0
- data/lib/tramway/config.rb +2 -1
- data/lib/tramway/configs/entity.rb +21 -8
- data/lib/tramway/decorators/class_helper.rb +17 -8
- data/lib/tramway/helpers/component_helper.rb +12 -0
- data/lib/tramway/version.rb +1 -1
- metadata +48 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7383199c539bbfdb97568068d0568fbf9366f2d3d6069258f2b96182c31bf2b4
|
|
4
|
+
data.tar.gz: d2320192e25efa23450e4e33730d869fe6c94b8bf8af885a494c4dfc5e808c44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70f4ea1f899fbf291fafb530129d5b4778b114d7f4c17115192c0f90a35269f871e7d23cd494361b175811d50542b746079abd0e1aacdd65963eb892f984f25b
|
|
7
|
+
data.tar.gz: 3db27c125714d2f67500e703fe10bc3497926672b943b34ea9d222e1832a7976b589cb0e502b0ab7a8400fb9616c784007c109facbf52f859e072bc51291420f
|
data/README.md
CHANGED
|
@@ -17,22 +17,25 @@ export default class Multiselect extends Controller {
|
|
|
17
17
|
|
|
18
18
|
connect() {
|
|
19
19
|
this.dropdownState = 'closed';
|
|
20
|
-
this.
|
|
20
|
+
this.items = JSON.parse(this.element.dataset.items).map((item, index) => {
|
|
21
21
|
return {
|
|
22
|
+
index,
|
|
22
23
|
text: item.text,
|
|
23
|
-
value: item.value.toString()
|
|
24
|
+
value: item.value.toString(),
|
|
25
|
+
selected: false
|
|
24
26
|
}
|
|
25
27
|
});
|
|
26
28
|
|
|
27
29
|
const initialValues = this.element.dataset.value === undefined ? [] : this.element.dataset.value.split(',')
|
|
28
|
-
this.selectedItems = this.
|
|
29
|
-
this.
|
|
30
|
+
this.selectedItems = this.items.filter(item => initialValues.includes(item.value));
|
|
31
|
+
this.items = this.items.filter(item => !initialValues.includes(item.value));
|
|
30
32
|
|
|
31
33
|
this.renderSelectedItems();
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
renderSelectedItems() {
|
|
35
37
|
const allItems = this.fillTemplate(this.element.dataset.selectedItemTemplate, this.selectedItems);
|
|
38
|
+
|
|
36
39
|
this.showSelectedAreaTarget.innerHTML = allItems;
|
|
37
40
|
this.showSelectedAreaTarget.insertAdjacentHTML("beforeEnd", this.input());
|
|
38
41
|
this.updateInputOptions();
|
|
@@ -86,25 +89,22 @@ export default class Multiselect extends Controller {
|
|
|
86
89
|
get template() {
|
|
87
90
|
return this.element.dataset.dropdownContainer.replace(
|
|
88
91
|
/{{content}}/g,
|
|
89
|
-
this.fillTemplate(this.element.dataset.itemContainer, this.
|
|
92
|
+
this.fillTemplate(this.element.dataset.itemContainer, this.items.filter(item => !item.selected))
|
|
90
93
|
);
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
toggleItem({ currentTarget }) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (itemIndex !== -1) {
|
|
101
|
-
this.selectedItems = this.selectedItems.filter((_, index) => index !== itemIndex);
|
|
97
|
+
const itemIndex = this.items.findIndex(x => x.value === currentTarget.dataset.value);
|
|
98
|
+
const itemSelectedIndex = this.selectedItems.findIndex(x => x.value === currentTarget.dataset.value);
|
|
99
|
+
|
|
100
|
+
if (itemSelectedIndex !== -1) {
|
|
101
|
+
this.selectedItems = this.selectedItems.filter((_, index) => index !== itemSelectedIndex);
|
|
102
|
+
this.items[itemIndex].selected = false;
|
|
102
103
|
} else {
|
|
103
|
-
this.selectedItems.push(
|
|
104
|
+
this.selectedItems.push(this.items[itemIndex]);
|
|
105
|
+
this.items[itemIndex].selected = true;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
this.unselectedItems = this.unselectedItems.filter(x => x.value !== item.value);
|
|
107
|
-
|
|
108
108
|
this.renderSelectedItems();
|
|
109
109
|
this.rerenderItems();
|
|
110
110
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class TableRowPreview extends Controller {
|
|
4
|
+
connect() {
|
|
5
|
+
this.items = JSON.parse(this.element.dataset.items || '{}');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
toggle() {
|
|
9
|
+
const rollUp = document.getElementById("roll-up");
|
|
10
|
+
|
|
11
|
+
const existingTable = rollUp.querySelector(".div-table");
|
|
12
|
+
if (existingTable) {
|
|
13
|
+
existingTable.remove();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const existingTitle = rollUp.querySelector("h3");
|
|
17
|
+
if (existingTitle) {
|
|
18
|
+
existingTitle.remove();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Object.keys(this.items).length === 0) return;
|
|
22
|
+
|
|
23
|
+
const titleText = document.createElement("h3");
|
|
24
|
+
|
|
25
|
+
titleText.classList.add("text-xl");
|
|
26
|
+
titleText.classList.add("text-white");
|
|
27
|
+
titleText.classList.add("py-4");
|
|
28
|
+
titleText.classList.add("px-4");
|
|
29
|
+
titleText.textContent = Object.values(this.items)[0];
|
|
30
|
+
|
|
31
|
+
const table = this.createTable(this.items);
|
|
32
|
+
|
|
33
|
+
rollUp.insertAdjacentElement('afterbegin', table);
|
|
34
|
+
rollUp.insertAdjacentElement('afterbegin', titleText);
|
|
35
|
+
|
|
36
|
+
rollUp.classList.toggle("hidden");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
close() {
|
|
40
|
+
const rollUp = document.getElementById("roll-up");
|
|
41
|
+
rollUp.classList.add("hidden");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
createTable(items) {
|
|
45
|
+
const table = document.createElement("div");
|
|
46
|
+
table.classList.add("div-table");
|
|
47
|
+
table.classList.add("text-white");
|
|
48
|
+
table.classList.add("px-2");
|
|
49
|
+
|
|
50
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
51
|
+
const rows = this.createTableRow(key, value);
|
|
52
|
+
|
|
53
|
+
rows.forEach((row) => table.appendChild(row));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return table;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
createTableRow(key, value) {
|
|
60
|
+
const keyRow = document.createElement("div");
|
|
61
|
+
keyRow.classList.add("div-table-row");
|
|
62
|
+
keyRow.classList.add("bg-purple-300");
|
|
63
|
+
keyRow.classList.add("text-purple-700");
|
|
64
|
+
keyRow.classList.add("dark:text-white");
|
|
65
|
+
keyRow.classList.add("dark:bg-gray-700");
|
|
66
|
+
keyRow.classList.add("px-2");
|
|
67
|
+
keyRow.classList.add("py-1");
|
|
68
|
+
keyRow.classList.add("text-xs");
|
|
69
|
+
keyRow.classList.add("font-semibold");
|
|
70
|
+
keyRow.textContent = key;
|
|
71
|
+
|
|
72
|
+
const valueRow = document.createElement("div");
|
|
73
|
+
valueRow.classList.add("div-table-row");
|
|
74
|
+
valueRow.classList.add("dark:bg-gray-800");
|
|
75
|
+
valueRow.classList.add("px-2");
|
|
76
|
+
valueRow.classList.add("py-2");
|
|
77
|
+
valueRow.textContent = value;
|
|
78
|
+
|
|
79
|
+
return [keyRow, valueRow];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { TableRowPreview };
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
require 'view_component'
|
|
4
4
|
|
|
5
5
|
# Base TailwindComponent. Contains base features for all tailwind components
|
|
6
|
-
class TailwindComponent <
|
|
7
|
-
extend Dry::Initializer[undefined: false]
|
|
8
|
-
|
|
6
|
+
class TailwindComponent < Tramway::Component::Base
|
|
9
7
|
option :input
|
|
10
8
|
option :attribute
|
|
11
9
|
option :value, optional: true
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
|
|
3
|
+
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
|
-
.flex.flex-col.
|
|
5
|
+
.flex.flex-col.relative{ data: multiselect_hash, id: "#{@for}_multiselect" }
|
|
6
6
|
.min-w-96.w-fit
|
|
7
7
|
.p-1.flex.border.border-gray-200.bg-white.rounded{ data: { "multiselect-target" => "dropdown" } }
|
|
8
8
|
.flex.flex-auto.flex-wrap{ data: { "multiselect-target" => "showSelectedArea" } }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
|
|
3
|
+
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
= @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: 'bg-white border border-gray-300 text-gray-700 py-2.5 px-2 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent'))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
|
|
3
|
+
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
= @input.call @attribute, **@options.merge(class: 'w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:border-red-500'), value: @value
|
|
@@ -11,7 +11,7 @@ module Tailwinds
|
|
|
11
11
|
def initialize(**options)
|
|
12
12
|
@href = options[:href]
|
|
13
13
|
@method = options[:method]
|
|
14
|
-
@style = 'text-white hover:bg-
|
|
14
|
+
@style = 'text-white hover:bg-gray-300 hover:text-gray-800 px-4 py-2 rounded whitespace-nowrap'
|
|
15
15
|
@options = options.except(:href, :method)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -10,7 +10,7 @@ module Tailwinds
|
|
|
10
10
|
class LinkComponent < TailwindComponent
|
|
11
11
|
def initialize(**options)
|
|
12
12
|
@href = options[:href]
|
|
13
|
-
@style = 'text-white hover:bg-
|
|
13
|
+
@style = 'text-white hover:bg-gray-300 hover:text-gray-800 px-4 py-2 rounded whitespace-nowrap'
|
|
14
14
|
@options = Rules::TurboHtmlAttributesRules.prepare_turbo_html_attributes(options:)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -1,16 +1,38 @@
|
|
|
1
|
-
%nav.py-4.px-8.flex.justify-between.items-center{ class: "bg-#{@color}" }
|
|
2
|
-
-
|
|
3
|
-
.
|
|
4
|
-
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
1
|
+
%nav.py-4.px-4.sm:px-8.flex.justify-between.items-center.dark:bg-gray-800{ class: "bg-#{@color}" }
|
|
2
|
+
.flex.justify-between.w-full
|
|
3
|
+
- if @title[:text].present? || @left_items.present?
|
|
4
|
+
.flex.items-center
|
|
5
|
+
- if @title[:text].present?
|
|
6
|
+
= link_to @title[:link] do
|
|
7
|
+
.text-xl.text-white.font-bold
|
|
8
|
+
= @title[:text]
|
|
9
|
+
- if @left_items.present?
|
|
10
|
+
%ul.hidden.sm:flex.items-center.space-x-4.ml-4
|
|
11
|
+
- @left_items.each do |item|
|
|
12
|
+
= item
|
|
13
|
+
|
|
14
|
+
.block.sm:hidden
|
|
15
|
+
%button#mobile-menu-button.text-white.focus:outline-none
|
|
16
|
+
☰
|
|
17
|
+
|
|
18
|
+
- if @right_items.present?
|
|
19
|
+
%ul.hidden.sm:flex.items-center.space-x-4
|
|
20
|
+
- @right_items.each do |item|
|
|
21
|
+
= item
|
|
22
|
+
|
|
23
|
+
#mobile-menu.hidden.flex-col.sm:hidden.dark:bg-gray-800
|
|
24
|
+
- if @left_items.present?
|
|
25
|
+
%ul.flex.flex-col.space-y-2
|
|
26
|
+
- @left_items.each do |item|
|
|
27
|
+
= item
|
|
12
28
|
|
|
13
29
|
- if @right_items.present?
|
|
14
|
-
%ul.flex.
|
|
30
|
+
%ul.flex.flex-col.space-y-2
|
|
15
31
|
- @right_items.each do |item|
|
|
16
32
|
= item
|
|
33
|
+
|
|
34
|
+
:javascript
|
|
35
|
+
document.getElementById('mobile-menu-button').addEventListener('click', function() {
|
|
36
|
+
var menu = document.getElementById('mobile-menu');
|
|
37
|
+
menu.classList.toggle('hidden');
|
|
38
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailwinds
|
|
4
|
+
module Pagination
|
|
5
|
+
# Base component for rendering a Kaminari pagination
|
|
6
|
+
class Base < Tramway::Component::Base
|
|
7
|
+
option :current_page
|
|
8
|
+
option :url
|
|
9
|
+
option :remote
|
|
10
|
+
|
|
11
|
+
# :reek:UtilityFunction { enabled: false }
|
|
12
|
+
def pagination_classes(klass: nil)
|
|
13
|
+
default_classes = ['cursor-pointer', 'px-3', 'py-2', 'font-medium', 'text-purple-700', 'bg-white',
|
|
14
|
+
'rounded-md', 'hover:bg-purple-100', 'dark:text-white', 'dark:bg-gray-800',
|
|
15
|
+
'dark:hover:bg-gray-700']
|
|
16
|
+
|
|
17
|
+
(default_classes + [klass]).join(' ')
|
|
18
|
+
end
|
|
19
|
+
# :reek:UtilityFunction { enabled: true }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
- unless current_page.first?
|
|
2
|
+
= link_to helpers.t('views.pagination.previous').html_safe, url, rel: 'prev', remote:, class: pagination_classes(klass: 'prev hidden sm:flex')
|
|
3
|
+
= link_to '🠔', url, rel: 'prev', remote:, class: pagination_classes(klass: 'prev sm:hidden font-bold')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
:css
|
|
2
|
+
@keyframes roll-up {
|
|
3
|
+
0% {
|
|
4
|
+
transform: translateY(100%);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
100% {
|
|
8
|
+
transform: translateY(0%);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.animate-roll-up {
|
|
13
|
+
animation: roll-up 0.5s ease-in-out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#roll-up.fixed.hidden.inset-x-0.bottom-0.bg-purple-700.shadow-lg.z-50.dark:bg-gray-800.animate-roll-up{ class: 'h-1/2' }
|
|
17
|
+
%button.absolute.top-4.text-white.right-4.hover:text-gray-700.dark:text-gray-400.dark:hover:text-gray-200{ "data-action" => "click->preview#close", "data-controller" => "preview" }
|
|
18
|
+
✕
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
- if cells.any?
|
|
3
|
+
- cols = cells.count.times.map { |item| "1fr" }.join(",")
|
|
4
|
+
|
|
5
|
+
= row_tag class: "div-table-row hidden md:grid gap-4 bg-white border-b dark:bg-gray-800 dark:border-gray-700 grid-cols-[#{cols}]" do
|
|
6
|
+
- cells.each do |(_, value)|
|
|
7
|
+
.div-table-cell.px-6.py-4.font-medium.text-gray-900.whitespace-nowrap.dark:text-white.text-xs.sm:text-base
|
|
8
|
+
= value
|
|
9
|
+
|
|
10
|
+
.div-table-row.border-b.dark:bg-gray-800.dark:border-gray-700.md:hidden.mb-2{ "data-action" => "click->preview#toggle", "data-controller" => "preview", "data-items" => cells.to_json }
|
|
11
|
+
.w-full.p-4.bg-purple-100.text-gray-700.dark:bg-gray-700.dark:text-gray-400
|
|
12
|
+
= cells.values.first
|
|
13
|
+
|
|
14
|
+
.flex.overflow-x-auto.whitespace-nowrap
|
|
15
|
+
- cells.each_with_index do |(_, value), index|
|
|
16
|
+
- next if index == 0
|
|
17
|
+
|
|
18
|
+
.text-gray-900.dark:text-white.p-4.text-xs.sm:text-base.inline-block.w-auto
|
|
19
|
+
= value
|
|
20
|
+
- else
|
|
21
|
+
- cols = Nokogiri::HTML.fragment(content).children.css('> div').count.times.map { |item| "1fr" }.join(",")
|
|
22
|
+
|
|
23
|
+
%div{ class: "div-table-row hidden md:grid gap-4 bg-white border-b dark:bg-gray-800 dark:border-gray-700 grid-cols-[#{cols}]" }
|
|
24
|
+
= content
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tailwinds
|
|
4
|
+
module Table
|
|
5
|
+
# Component for rendering a row in a table
|
|
6
|
+
class RowComponent < Tramway::Component::Base
|
|
7
|
+
option :cells, optional: true, default: -> { [] }
|
|
8
|
+
option :href, optional: true
|
|
9
|
+
|
|
10
|
+
def row_tag(**options, &)
|
|
11
|
+
if href.present?
|
|
12
|
+
klass = "#{options[:class] || ''} cursor-pointer hover:bg-gray-700"
|
|
13
|
+
|
|
14
|
+
link_to(href, options.merge(class: klass)) do
|
|
15
|
+
yield if block_given?
|
|
16
|
+
end
|
|
17
|
+
else
|
|
18
|
+
tag.div(**options) do
|
|
19
|
+
yield if block_given?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
module Component
|
|
5
|
+
# You can use this class as a base for all your components
|
|
6
|
+
class Base < ViewComponent::Base
|
|
7
|
+
extend Dry::Initializer[undefined: false]
|
|
8
|
+
include Tramway::Helpers::ComponentHelper
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
# Main controller for entities pages
|
|
5
|
+
class EntitiesController < Tramway.config.application_controller.constantize
|
|
6
|
+
helper Tramway::ApplicationHelper
|
|
7
|
+
include Rails.application.routes.url_helpers
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@entities = model_class.page(params[:page])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def model_class
|
|
16
|
+
@model_class ||= params[:entity].classify.constantize
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tramway/helpers/component_helper'
|
|
4
|
+
|
|
5
|
+
module Tramway
|
|
6
|
+
# Main helper module for Tramway entities pages
|
|
7
|
+
module ApplicationHelper
|
|
8
|
+
include Tramway::Decorators::ClassHelper
|
|
9
|
+
include Tramway::Helpers::ComponentHelper
|
|
10
|
+
|
|
11
|
+
def page_title
|
|
12
|
+
@model_class.model_name.human.pluralize
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
-# available local variables
|
|
3
|
-
-# url: url to the first page
|
|
4
|
-
-# current_page: a page object for the currently displayed page
|
|
5
|
-
-# total_pages: total number of pages
|
|
6
|
-
-# per_page: number of items to fetch per page
|
|
7
|
-
-# remote: data-remote
|
|
8
|
-
%span.first{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
|
|
9
|
-
= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote
|
|
1
|
+
= component 'tailwinds/pagination/first_page', current_page:, url:, remote:
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
-# available local variables
|
|
3
|
-
-# current_page: a page object for the currently displayed page
|
|
4
|
-
-# total_pages: total number of pages
|
|
5
|
-
-# per_page: number of items to fetch per page
|
|
6
|
-
-# remote: data-remote
|
|
7
|
-
%span.page.gap{ class: 'px-3 py-2 text-sm font-medium text-purple-700' }
|
|
8
|
-
= t('views.pagination.truncate').html_safe
|
|
1
|
+
= component 'tailwinds/pagination/gap'
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
-# available local variables
|
|
3
|
-
-# url: url to the last page
|
|
4
|
-
-# current_page: a page object for the currently displayed page
|
|
5
|
-
-# total_pages: total number of pages
|
|
6
|
-
-# per_page: number of items to fetch per page
|
|
7
|
-
-# remote: data-remote
|
|
8
|
-
%span.last{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
|
|
9
|
-
= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote
|
|
1
|
+
= component 'tailwinds/pagination/last_page', current_page:, url:, remote:
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
-# available local variables
|
|
3
|
-
-# url: url to the next page
|
|
4
|
-
-# current_page: a page object for the currently displayed page
|
|
5
|
-
-# total_pages: total number of pages
|
|
6
|
-
-# per_page: number of items to fetch per page
|
|
7
|
-
-# remote: data-remote
|
|
8
|
-
%span.next{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
|
|
9
|
-
= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote
|
|
1
|
+
= component 'tailwinds/pagination/next_page', current_page:, url:, remote:
|
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
-# available local variables
|
|
3
|
-
-# page: a page object for "this" page
|
|
4
|
-
-# url: url to this page
|
|
5
|
-
-# current_page: a page object for the currently displayed page
|
|
6
|
-
-# total_pages: total number of pages
|
|
7
|
-
-# per_page: number of items to fetch per page
|
|
8
|
-
-# remote: data-remote
|
|
9
|
-
- if page.current?
|
|
10
|
-
%span{class: "px-3 py-2 font-medium rounded-md bg-purple-500 text-white" }
|
|
11
|
-
= page
|
|
12
|
-
- else
|
|
13
|
-
%span{class: "cursor px-3 py-2 font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100"}
|
|
14
|
-
= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel}
|
|
1
|
+
= component 'tailwinds/pagination/page', current_page:, url:, remote:, page:
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
-# available local variables
|
|
3
|
-
-# url: url to the previous page
|
|
4
|
-
-# current_page: a page object for the currently displayed page
|
|
5
|
-
-# total_pages: total number of pages
|
|
6
|
-
-# per_page: number of items to fetch per page
|
|
7
|
-
-# remote: data-remote
|
|
8
|
-
%span.prev{ class: 'px-3 py-2 text-sm font-medium text-purple-700 bg-white rounded-md hover:bg-purple-100' }
|
|
9
|
-
= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote
|
|
1
|
+
= component 'tailwinds/pagination/prev_page', current_page:, url:, remote:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.w-full
|
|
2
|
+
- content_for :title, page_title
|
|
3
|
+
|
|
4
|
+
.flex.justify-between.items-center.md:mt-4.mt-2
|
|
5
|
+
%h1.font-bold.text-4xl.dark:text-white
|
|
6
|
+
= page_title
|
|
7
|
+
|
|
8
|
+
- if Tramway.config.pagination[:enabled]
|
|
9
|
+
= paginate @entities
|
|
10
|
+
|
|
11
|
+
- if decorator_class(@entities).list_attributes.empty?
|
|
12
|
+
%p.text-center.mt-10
|
|
13
|
+
You should fill class-level method `self.list_attributes` inside your
|
|
14
|
+
= decorator_class_name(@entities)
|
|
15
|
+
|
|
16
|
+
= component 'tailwinds/table' do
|
|
17
|
+
= component 'tailwinds/table/header', headers: decorator_class(@entities).list_attributes.map { |attribute| @model_class.human_attribute_name(attribute) }
|
|
18
|
+
- @entities.each do |item|
|
|
19
|
+
= render 'entity', entity: item
|
|
20
|
+
|
|
21
|
+
.flex.mt-4
|
|
22
|
+
= paginate @entities
|
data/config/routes.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Lint/EmptyBlock
|
|
4
3
|
Tramway::Engine.routes.draw do
|
|
4
|
+
Tramway.config.entities.each do |entity|
|
|
5
|
+
resources entity.name.pluralize.to_sym, only: entity.pages, controller: :entities, defaults: { entity: entity.name }
|
|
6
|
+
end
|
|
5
7
|
end
|
|
6
|
-
# rubocop:enable Lint/EmptyBlock
|
|
@@ -43,6 +43,10 @@ module Tramway
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
def list_attributes
|
|
47
|
+
[]
|
|
48
|
+
end
|
|
49
|
+
|
|
46
50
|
include Tramway::Decorators::AssociationClassMethods
|
|
47
51
|
end
|
|
48
52
|
|
|
@@ -51,5 +55,7 @@ module Tramway
|
|
|
51
55
|
|
|
52
56
|
"#{underscored_class_name.pluralize}/#{underscored_class_name}"
|
|
53
57
|
end
|
|
58
|
+
|
|
59
|
+
def show_path = nil
|
|
54
60
|
end
|
|
55
61
|
end
|
data/lib/tramway/config.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Tramway
|
|
|
7
7
|
# Tramway is an entity-based framework
|
|
8
8
|
class Entity < Dry::Struct
|
|
9
9
|
attribute :name, Types::Coercible::String
|
|
10
|
+
attribute? :pages, Types::Array.of(Types::Symbol).default([].freeze)
|
|
10
11
|
attribute? :route, Tramway::Configs::Entities::Route
|
|
11
12
|
|
|
12
13
|
# Route Struct contains implemented in Tramway CRUD and helpful routes for the entity
|
|
@@ -16,13 +17,13 @@ module Tramway
|
|
|
16
17
|
HumanNameStruct = Struct.new(:single, :plural)
|
|
17
18
|
|
|
18
19
|
def routes
|
|
19
|
-
RouteStruct.new(
|
|
20
|
+
RouteStruct.new(route_helper_method)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def human_name
|
|
23
24
|
single, plural = if model_class.present?
|
|
24
25
|
model_name = model_class.model_name.human
|
|
25
|
-
[model_name, model_name
|
|
26
|
+
[model_name, pluralized(model_name)]
|
|
26
27
|
else
|
|
27
28
|
[name.capitalize, name.pluralize.capitalize]
|
|
28
29
|
end
|
|
@@ -32,8 +33,14 @@ module Tramway
|
|
|
32
33
|
|
|
33
34
|
private
|
|
34
35
|
|
|
36
|
+
def pluralized(model_name)
|
|
37
|
+
local_plural = I18n.t("#{name}.many", scope: 'activerecord.plural.models', default: nil)
|
|
38
|
+
|
|
39
|
+
local_plural.present? ? local_plural : model_name.pluralize
|
|
40
|
+
end
|
|
41
|
+
|
|
35
42
|
def model_class
|
|
36
|
-
name.
|
|
43
|
+
name.classify.constantize
|
|
37
44
|
rescue StandardError
|
|
38
45
|
nil
|
|
39
46
|
end
|
|
@@ -41,11 +48,17 @@ module Tramway
|
|
|
41
48
|
def route_helper_method
|
|
42
49
|
underscored_name = name.parameterize.pluralize.underscore
|
|
43
50
|
|
|
44
|
-
if route.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
method_name = if pages.include?(:index) || route.blank?
|
|
52
|
+
"#{underscored_name}_path"
|
|
53
|
+
else
|
|
54
|
+
route.helper_method_by(underscored_name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
route_helper_engine.routes.url_helpers.public_send(method_name)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def route_helper_engine
|
|
61
|
+
pages.include?(:index) ? Tramway::Engine : Rails.application
|
|
49
62
|
end
|
|
50
63
|
end
|
|
51
64
|
end
|
|
@@ -6,19 +6,28 @@ module Tramway
|
|
|
6
6
|
module ClassHelper
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
-
def decorator_class(object_or_array, decorator)
|
|
9
|
+
def decorator_class(object_or_array, decorator = nil)
|
|
10
10
|
if decorator.present?
|
|
11
11
|
decorator
|
|
12
12
|
else
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Tramway::Decorators::NameBuilder.default_decorator_class_name(klass).constantize
|
|
13
|
+
begin
|
|
14
|
+
class_name = decorator_class_name(object_or_array)
|
|
15
|
+
class_name.constantize
|
|
16
|
+
rescue NameError
|
|
17
|
+
raise NameError, "You should define #{class_name} decorator class."
|
|
18
|
+
end
|
|
20
19
|
end
|
|
21
20
|
end
|
|
21
|
+
|
|
22
|
+
def decorator_class_name(object_or_array)
|
|
23
|
+
klass = if Tramway::Decorators::CollectionDecorators.collection?(object_or_array)
|
|
24
|
+
object_or_array.first.class
|
|
25
|
+
else
|
|
26
|
+
object_or_array.class
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Tramway::Decorators::NameBuilder.default_decorator_class_name(klass)
|
|
30
|
+
end
|
|
22
31
|
end
|
|
23
32
|
end
|
|
24
33
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
module Helpers
|
|
5
|
+
# Provides a helper method `component` that allows to render components conveniently
|
|
6
|
+
module ComponentHelper
|
|
7
|
+
def component(name, *, **, &)
|
|
8
|
+
render("#{name}_component".classify.constantize.new(*, **), &)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/tramway/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tramway
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.5'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kalashnikovisme
|
|
@@ -9,8 +9,22 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2024-
|
|
12
|
+
date: 2024-12-09 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: anyway_config
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - ">="
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '0'
|
|
14
28
|
- !ruby/object:Gem::Dependency
|
|
15
29
|
name: dry-initializer
|
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -98,11 +112,14 @@ files:
|
|
|
98
112
|
- README.md
|
|
99
113
|
- Rakefile
|
|
100
114
|
- app/assets/javascripts/tramway/multiselect_controller.js
|
|
115
|
+
- app/assets/javascripts/tramway/table_row_preview_controller.js
|
|
101
116
|
- app/components/tailwind_component.html.haml
|
|
102
117
|
- app/components/tailwind_component.rb
|
|
103
118
|
- app/components/tailwinds/form/builder.rb
|
|
104
119
|
- app/components/tailwinds/form/file_field_component.html.haml
|
|
105
120
|
- app/components/tailwinds/form/file_field_component.rb
|
|
121
|
+
- app/components/tailwinds/form/label_component.html.haml
|
|
122
|
+
- app/components/tailwinds/form/label_component.rb
|
|
106
123
|
- app/components/tailwinds/form/multiselect/dropdown_container.html.haml
|
|
107
124
|
- app/components/tailwinds/form/multiselect/dropdown_container.rb
|
|
108
125
|
- app/components/tailwinds/form/multiselect/item_container.html.haml
|
|
@@ -125,6 +142,32 @@ files:
|
|
|
125
142
|
- app/components/tailwinds/nav/item/link_component.rb
|
|
126
143
|
- app/components/tailwinds/navbar_component.html.haml
|
|
127
144
|
- app/components/tailwinds/navbar_component.rb
|
|
145
|
+
- app/components/tailwinds/pagination/base.rb
|
|
146
|
+
- app/components/tailwinds/pagination/first_page_component.html.haml
|
|
147
|
+
- app/components/tailwinds/pagination/first_page_component.rb
|
|
148
|
+
- app/components/tailwinds/pagination/gap_component.html.haml
|
|
149
|
+
- app/components/tailwinds/pagination/gap_component.rb
|
|
150
|
+
- app/components/tailwinds/pagination/last_page_component.html.haml
|
|
151
|
+
- app/components/tailwinds/pagination/last_page_component.rb
|
|
152
|
+
- app/components/tailwinds/pagination/next_page_component.html.haml
|
|
153
|
+
- app/components/tailwinds/pagination/next_page_component.rb
|
|
154
|
+
- app/components/tailwinds/pagination/page_component.html.haml
|
|
155
|
+
- app/components/tailwinds/pagination/page_component.rb
|
|
156
|
+
- app/components/tailwinds/pagination/prev_page_component.html.haml
|
|
157
|
+
- app/components/tailwinds/pagination/prev_page_component.rb
|
|
158
|
+
- app/components/tailwinds/table/cell_component.html.haml
|
|
159
|
+
- app/components/tailwinds/table/cell_component.rb
|
|
160
|
+
- app/components/tailwinds/table/header_component.html.haml
|
|
161
|
+
- app/components/tailwinds/table/header_component.rb
|
|
162
|
+
- app/components/tailwinds/table/row/preview_component.html.haml
|
|
163
|
+
- app/components/tailwinds/table/row/preview_component.rb
|
|
164
|
+
- app/components/tailwinds/table/row_component.html.haml
|
|
165
|
+
- app/components/tailwinds/table/row_component.rb
|
|
166
|
+
- app/components/tailwinds/table_component.html.haml
|
|
167
|
+
- app/components/tailwinds/table_component.rb
|
|
168
|
+
- app/components/tramway/component/base.rb
|
|
169
|
+
- app/controllers/tramway/entities_controller.rb
|
|
170
|
+
- app/helpers/tramway/application_helper.rb
|
|
128
171
|
- app/views/kaminari/_first_page.html.haml
|
|
129
172
|
- app/views/kaminari/_gap.html.haml
|
|
130
173
|
- app/views/kaminari/_last_page.html.haml
|
|
@@ -132,6 +175,8 @@ files:
|
|
|
132
175
|
- app/views/kaminari/_page.html.haml
|
|
133
176
|
- app/views/kaminari/_paginator.html.haml
|
|
134
177
|
- app/views/kaminari/_prev_page.html.haml
|
|
178
|
+
- app/views/tramway/entities/_entity.html.haml
|
|
179
|
+
- app/views/tramway/entities/index.html.haml
|
|
135
180
|
- config/routes.rb
|
|
136
181
|
- lib/rules/turbo_html_attributes_rules.rb
|
|
137
182
|
- lib/tasks/tramway_tasks.rake
|
|
@@ -151,6 +196,7 @@ files:
|
|
|
151
196
|
- lib/tramway/forms/class_helper.rb
|
|
152
197
|
- lib/tramway/forms/normalizations.rb
|
|
153
198
|
- lib/tramway/forms/properties.rb
|
|
199
|
+
- lib/tramway/helpers/component_helper.rb
|
|
154
200
|
- lib/tramway/helpers/decorate_helper.rb
|
|
155
201
|
- lib/tramway/helpers/form_helper.rb
|
|
156
202
|
- lib/tramway/helpers/navbar_helper.rb
|