tramway 0.4.9.3 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|