tramway 0.5.4.1 → 0.5.5.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 +42 -0
- data/app/components/tailwind_component.rb +32 -0
- data/app/components/tailwinds/form/builder.rb +53 -20
- data/app/components/tailwinds/form/file_field_component.html.haml +4 -1
- data/app/components/tailwinds/form/multiselect/dropdown_container.html.haml +1 -1
- data/app/components/tailwinds/form/multiselect/item_container.html.haml +3 -3
- data/app/components/tailwinds/form/multiselect/select_as_input.html.haml +4 -1
- data/app/components/tailwinds/form/multiselect/select_as_input.rb +1 -0
- data/app/components/tailwinds/form/multiselect/selected_item_template.html.haml +1 -1
- data/app/components/tailwinds/form/multiselect_component.html.haml +3 -3
- data/app/components/tailwinds/form/multiselect_component.rb +8 -1
- data/app/components/tailwinds/form/number_field_component.html.haml +8 -0
- data/app/components/tailwinds/form/number_field_component.rb +9 -0
- data/app/components/tailwinds/form/select_component.html.haml +5 -1
- data/app/components/tailwinds/form/submit_button_component.html.haml +4 -1
- data/app/components/tailwinds/form/submit_button_component.rb +11 -3
- data/app/components/tailwinds/form/text_area_component.html.haml +4 -1
- data/app/components/tailwinds/form/text_field_component.html.haml +4 -1
- data/app/components/tailwinds/table/header_component.html.haml +1 -1
- data/app/components/tailwinds/table/header_component.rb +6 -1
- data/app/components/tailwinds/table/row_component.rb +2 -6
- data/app/components/tailwinds/table_component.html.haml +1 -1
- data/app/controllers/tramway/entities_controller.rb +11 -5
- data/config/routes.rb +4 -4
- data/lib/tramway/base_form.rb +1 -1
- data/lib/tramway/decorators/association.rb +1 -1
- data/lib/tramway/decorators/class_helper.rb +18 -16
- data/lib/tramway/forms/properties.rb +6 -10
- data/lib/tramway/helpers/views_helper.rb +7 -2
- data/lib/tramway/navbar.rb +5 -5
- data/lib/tramway/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e2e7bd75d61196a4ba4302fe3c05d5dfce6928a35ea0d97022b9170ce911ca8
|
|
4
|
+
data.tar.gz: 1b89b1fb5a18ae6d28c8afb1b8a1d94da832f0855c1acf1f5ab4f9fb0f1d4bd7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4565144363a3419833bb267231fc403ff49c1da50caa3ee4ec936d6a2245fc2ddf89ab111af33cc7a5759cd9bc51d90cc456e5108acc78b73e99ca7ea5107158
|
|
7
|
+
data.tar.gz: a83591d638a439dcd659474cfd50b73f9f292bff11fd2e18987c821df25888a9de4ae68d16fb2d7048eb36949869350339b8c3104ffaa71f92f97042d2a84ba9
|
data/README.md
CHANGED
|
@@ -14,6 +14,16 @@ Unite Ruby on Rails brilliance. Streamline development with Tramway.
|
|
|
14
14
|
* [Tailwind-styled pagination](https://github.com/Purple-Magic/tramway?tab=readme-ov-file#tailwind-styled-pagination-for-kaminari)
|
|
15
15
|
* [Articles](https://github.com/Purple-Magic/tramway#usage)
|
|
16
16
|
|
|
17
|
+
## Compatibility
|
|
18
|
+
|
|
19
|
+
Tramway is actively verified against the following Ruby and Rails versions.
|
|
20
|
+
|
|
21
|
+
| Ruby \ Rails | 7.1 | 7.2 | 8.0 | 8.1 |
|
|
22
|
+
| ------------- | --- | --- | --- | --- |
|
|
23
|
+
| 3.2 | ✅ | ✅ | ✅ | ✅ |
|
|
24
|
+
| 3.3 | ✅ | ✅ | ✅ | ✅ |
|
|
25
|
+
| 3.4 | ✅ | ✅ | ✅ | ✅ |
|
|
26
|
+
|
|
17
27
|
## Installation
|
|
18
28
|
Add this line to your application's Gemfile:
|
|
19
29
|
|
|
@@ -528,6 +538,38 @@ Tramway provides a responsive, tailwind-styled table with light and dark themes.
|
|
|
528
538
|
Another
|
|
529
539
|
```
|
|
530
540
|
|
|
541
|
+
`Tailwinds::TableComponent` accepts an optional `options` hash that is merged into the outer `.div-table` element. The hash is
|
|
542
|
+
forwarded as HTML attributes, so you can pass things like `id`, `data` attributes, or additional classes. If you do not supply
|
|
543
|
+
your own width utility (e.g. a class that starts with `w-`), the component automatically appends `w-full` to keep the table
|
|
544
|
+
responsive. This allows you to extend the default styling without losing the sensible defaults provided by the component.
|
|
545
|
+
|
|
546
|
+
```haml
|
|
547
|
+
= component 'tailwinds/table', options: { class: 'max-w-3xl border border-gray-200', data: { controller: 'table' } } do
|
|
548
|
+
= component 'tailwinds/table/header', headers: ['Name', 'Email']
|
|
549
|
+
= component 'tailwinds/table/row' do
|
|
550
|
+
= component 'tailwinds/table/cell' do
|
|
551
|
+
= user.name
|
|
552
|
+
= component 'tailwinds/table/cell' do
|
|
553
|
+
= user.email
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
When you render a header you can either pass the `headers:` array, as in the examples above, or render custom header content in
|
|
557
|
+
the block. `Tailwinds::Table::HeaderComponent` uses the length of the `headers` array to build the grid if the array is present.
|
|
558
|
+
If you omit the array and provide custom content, pass the `columns:` argument so the component knows how many grid columns to
|
|
559
|
+
generate.
|
|
560
|
+
|
|
561
|
+
```haml
|
|
562
|
+
= component 'tailwinds/table/header', columns: 4 do
|
|
563
|
+
= component 'tailwinds/table/cell' do
|
|
564
|
+
Custom header cell
|
|
565
|
+
= component 'tailwinds/table/cell' do
|
|
566
|
+
Another header cell
|
|
567
|
+
/ ...
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
With this approach you control the header layout while still benefiting from the default Tailwind grid classes that the header
|
|
571
|
+
component applies.
|
|
572
|
+
|
|
531
573
|
### Tailwind-styled forms
|
|
532
574
|
|
|
533
575
|
Tramway uses [Tailwind](https://tailwindcss.com/) by default. All UI helpers are implemented with [ViewComponent](https://github.com/viewcomponent/view_component).
|
|
@@ -10,4 +10,36 @@ class TailwindComponent < Tramway::Component::Base
|
|
|
10
10
|
option :options
|
|
11
11
|
option :label
|
|
12
12
|
option :for
|
|
13
|
+
option :size, default: -> { :middle }
|
|
14
|
+
|
|
15
|
+
SIZE_CLASSES = {
|
|
16
|
+
small: {
|
|
17
|
+
text_input: 'text-sm px-2 py-1',
|
|
18
|
+
select_input: 'text-sm px-2 py-1',
|
|
19
|
+
file_button: 'text-sm px-3 py-1',
|
|
20
|
+
submit_button: 'text-sm px-3 py-1',
|
|
21
|
+
multiselect_input: 'text-sm px-2 py-1'
|
|
22
|
+
},
|
|
23
|
+
middle: {
|
|
24
|
+
text_input: 'text-base px-3 py-2',
|
|
25
|
+
select_input: 'text-base px-3 py-2',
|
|
26
|
+
file_button: 'text-base px-4 py-2',
|
|
27
|
+
submit_button: 'text-base px-4 py-2',
|
|
28
|
+
multiselect_input: 'text-base px-3 py-2'
|
|
29
|
+
},
|
|
30
|
+
large: {
|
|
31
|
+
text_input: 'text-lg px-4 py-3',
|
|
32
|
+
select_input: 'text-lg px-4 py-3',
|
|
33
|
+
file_button: 'text-lg px-5 py-3',
|
|
34
|
+
submit_button: 'text-lg px-5 py-3',
|
|
35
|
+
multiselect_input: 'text-lg px-4 py-3'
|
|
36
|
+
}
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def size_class(key)
|
|
42
|
+
size_classes = SIZE_CLASSES.fetch(size) { SIZE_CLASSES[:middle] }
|
|
43
|
+
size_classes.fetch(key) { SIZE_CLASSES[:middle].fetch(key) }
|
|
44
|
+
end
|
|
13
45
|
end
|
|
@@ -5,59 +5,90 @@ module Tailwinds
|
|
|
5
5
|
# Provides Tailwind-styled forms
|
|
6
6
|
# :reek:InstanceVariableAssumption
|
|
7
7
|
class Builder < Tramway::Views::FormBuilder
|
|
8
|
+
def initialize(object_name, object, template, options)
|
|
9
|
+
super
|
|
10
|
+
|
|
11
|
+
@form_size = options[:size] || options['size'] || :middle
|
|
12
|
+
end
|
|
13
|
+
|
|
8
14
|
def text_field(attribute, **options, &)
|
|
15
|
+
sanitized_options = sanitize_options(options)
|
|
16
|
+
|
|
9
17
|
render(Tailwinds::Form::TextFieldComponent.new(
|
|
10
18
|
input: input(:text_field),
|
|
11
|
-
value: get_value(attribute,
|
|
12
|
-
**default_options(attribute,
|
|
19
|
+
value: get_value(attribute, sanitized_options),
|
|
20
|
+
**default_options(attribute, sanitized_options)
|
|
21
|
+
), &)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def number_field(attribute, **options, &)
|
|
25
|
+
sanitized_options = sanitize_options(options)
|
|
26
|
+
|
|
27
|
+
render(Tailwinds::Form::NumberFieldComponent.new(
|
|
28
|
+
input: input(:number_field),
|
|
29
|
+
value: get_value(attribute, sanitized_options),
|
|
30
|
+
**default_options(attribute, sanitized_options)
|
|
13
31
|
), &)
|
|
14
32
|
end
|
|
15
33
|
|
|
16
34
|
def text_area(attribute, **options, &)
|
|
35
|
+
sanitized_options = sanitize_options(options)
|
|
36
|
+
|
|
17
37
|
render(Tailwinds::Form::TextAreaComponent.new(
|
|
18
38
|
input: input(:text_area),
|
|
19
|
-
value: get_value(attribute,
|
|
20
|
-
**default_options(attribute,
|
|
39
|
+
value: get_value(attribute, sanitized_options),
|
|
40
|
+
**default_options(attribute, sanitized_options)
|
|
21
41
|
), &)
|
|
22
42
|
end
|
|
23
43
|
|
|
24
44
|
def password_field(attribute, **options, &)
|
|
45
|
+
sanitized_options = sanitize_options(options)
|
|
46
|
+
|
|
25
47
|
render(Tailwinds::Form::TextFieldComponent.new(
|
|
26
48
|
input: input(:password_field),
|
|
27
|
-
**default_options(attribute,
|
|
49
|
+
**default_options(attribute, sanitized_options)
|
|
28
50
|
), &)
|
|
29
51
|
end
|
|
30
52
|
|
|
31
53
|
def file_field(attribute, **options, &)
|
|
32
|
-
|
|
54
|
+
sanitized_options = sanitize_options(options)
|
|
55
|
+
input = super(attribute, **sanitized_options.merge(class: :hidden))
|
|
33
56
|
|
|
34
|
-
render(Tailwinds::Form::FileFieldComponent.new(input:, **default_options(attribute,
|
|
57
|
+
render(Tailwinds::Form::FileFieldComponent.new(input:, **default_options(attribute, sanitized_options)), &)
|
|
35
58
|
end
|
|
36
59
|
|
|
37
60
|
def select(attribute, collection, **options, &)
|
|
61
|
+
sanitized_options = sanitize_options(options)
|
|
62
|
+
|
|
38
63
|
render(Tailwinds::Form::SelectComponent.new(
|
|
39
64
|
input: input(:select),
|
|
40
|
-
value:
|
|
41
|
-
collection: explicitly_add_blank_option(collection,
|
|
42
|
-
**default_options(attribute,
|
|
65
|
+
value: sanitized_options[:selected] || object.public_send(attribute),
|
|
66
|
+
collection: explicitly_add_blank_option(collection, sanitized_options),
|
|
67
|
+
**default_options(attribute, sanitized_options)
|
|
43
68
|
), &)
|
|
44
69
|
end
|
|
45
70
|
|
|
46
71
|
def multiselect(attribute, collection, **options, &)
|
|
72
|
+
sanitized_options = sanitize_options(options)
|
|
73
|
+
|
|
47
74
|
render(Tailwinds::Form::MultiselectComponent.new(
|
|
48
75
|
input: input(:text_field),
|
|
49
|
-
value:
|
|
76
|
+
value: sanitized_options[:value] || sanitized_options[:selected] || object.public_send(attribute),
|
|
50
77
|
collection:,
|
|
51
|
-
**default_options(attribute,
|
|
78
|
+
**default_options(attribute, sanitized_options)
|
|
52
79
|
), &)
|
|
53
80
|
end
|
|
54
81
|
|
|
55
|
-
def submit(action,
|
|
56
|
-
|
|
82
|
+
def submit(action, **options, &)
|
|
83
|
+
sanitized_options = sanitize_options(options)
|
|
84
|
+
|
|
85
|
+
render(Tailwinds::Form::SubmitButtonComponent.new(action, size: form_size, **sanitized_options), &)
|
|
57
86
|
end
|
|
58
87
|
|
|
59
88
|
private
|
|
60
89
|
|
|
90
|
+
attr_reader :form_size
|
|
91
|
+
|
|
61
92
|
def input(method_name)
|
|
62
93
|
unbound_method = self.class.superclass.instance_method(method_name)
|
|
63
94
|
unbound_method.bind(self)
|
|
@@ -68,12 +99,7 @@ module Tailwinds
|
|
|
68
99
|
end
|
|
69
100
|
|
|
70
101
|
def default_options(attribute, options)
|
|
71
|
-
{
|
|
72
|
-
attribute:,
|
|
73
|
-
label: label_build(attribute, options),
|
|
74
|
-
for: for_id(attribute),
|
|
75
|
-
options:
|
|
76
|
-
}
|
|
102
|
+
{ attribute:, label: label_build(attribute, options), for: for_id(attribute), options:, size: form_size }
|
|
77
103
|
end
|
|
78
104
|
|
|
79
105
|
# :reek:UtilityFunction
|
|
@@ -88,6 +114,13 @@ module Tailwinds
|
|
|
88
114
|
"#{object_name}_#{attribute}"
|
|
89
115
|
end
|
|
90
116
|
|
|
117
|
+
def sanitize_options(options)
|
|
118
|
+
options.dup.tap do |opts|
|
|
119
|
+
opts.delete(:size)
|
|
120
|
+
opts.delete('size')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
91
124
|
# REMOVE IT. WE MUST UNDERSTAND WHY INCLUDE_BLANK DOES NOT WORK
|
|
92
125
|
# :reek:UtilityFunction
|
|
93
126
|
def explicitly_add_blank_option(collection, options)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
|
|
3
|
+
- base_classes = 'inline-block bg-blue-500 hover:bg-blue-700 text-white font-bold rounded cursor-pointer mt-4 '
|
|
4
|
+
- base_classes += 'dark:bg-blue-600 dark:hover:bg-blue-500'
|
|
5
|
+
- classes = "#{size_class(:file_button)} #{base_classes}"
|
|
6
|
+
%label{ for: @for, class: classes }
|
|
4
7
|
= @label
|
|
5
8
|
= @input
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#dropdown.absolute.shadow.top-11.bg-white.z-40.w-full.lef-0.rounded.max-h-select.overflow-y-auto{ data: { action: "click@window->multiselect#closeOnClickOutside" } }
|
|
1
|
+
#dropdown.absolute.shadow.top-11.bg-white.z-40.w-full.lef-0.rounded.max-h-select.overflow-y-auto.dark:bg-gray-800.dark:shadow-lg.dark:ring-1.dark:ring-gray-700{ data: { action: "click@window->multiselect#closeOnClickOutside" } }
|
|
2
2
|
.flex.flex-col.w-full
|
|
3
3
|
{{content}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
.cursor-pointer.w-full.border-gray-100.rounded-t.border-b.hover:bg-teal-100{ data: { action: "click->multiselect#toggleItem", text: "{{text}}", value: "{{value}}" } }
|
|
2
|
-
.flex.w-full.items-center.p-2.pl-2.border-transparent.border-l-2.relative.hover:border-teal-100
|
|
3
|
-
.w-full.items-center.flex
|
|
1
|
+
.cursor-pointer.w-full.border-gray-100.rounded-t.border-b.hover:bg-teal-100.dark:border-gray-700.dark:hover:bg-gray-700{ data: { action: "click->multiselect#toggleItem", text: "{{text}}", value: "{{value}}" } }
|
|
2
|
+
.flex.w-full.items-center.p-2.pl-2.border-transparent.border-l-2.relative.hover:border-teal-100.dark:hover:border-gray-600
|
|
3
|
+
.w-full.items-center.flex.dark:text-gray-100
|
|
4
4
|
.mx-2.leading-6
|
|
5
5
|
{{text}}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
.flex-1
|
|
2
|
-
|
|
2
|
+
- base_classes = 'bg-transparent appearance-none outline-none h-full w-full text-gray-800 hidden '
|
|
3
|
+
- base_classes += 'dark:text-white dark:placeholder-white'
|
|
4
|
+
- classes = "#{@size_class} #{base_classes}"
|
|
5
|
+
= @input.call(@attribute, @options.merge(placeholder: "{{placeholder}}", class: classes, data: { 'multiselect-target' => 'hiddenInput' }))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.flex.justify-center.items-center.m-1.font-medium.py-1.px-2.bg-white.rounded-full.text-teal-700.bg-teal-100.border.border-teal-300
|
|
1
|
+
.flex.justify-center.items-center.m-1.font-medium.py-1.px-2.bg-white.rounded-full.text-teal-700.bg-teal-100.border.border-teal-300.dark:bg-teal-900.dark:text-teal-100.dark:border-teal-700
|
|
2
2
|
.text-xs.font-normal.leading-none.max-w-full.flex-initial
|
|
3
3
|
{{text}}
|
|
4
4
|
.flex.flex-auto.flex-row-reverse
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
- if @label
|
|
3
3
|
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
|
-
.flex.flex-col.relative{ data: multiselect_hash, id: "#{@for}_multiselect" }
|
|
5
|
+
.flex.flex-col.relative.dark:text-gray-100{ data: multiselect_hash, id: "#{@for}_multiselect" }
|
|
6
6
|
.min-w-96.w-fit
|
|
7
|
-
.p-1.flex.border.border-gray-200.bg-white.rounded{ data: { "multiselect-target" => "dropdown" } }
|
|
7
|
+
.p-1.flex.border.border-gray-200.bg-white.rounded.dark:border-gray-600.dark:bg-gray-800{ data: { "multiselect-target" => "dropdown" } }
|
|
8
8
|
.flex.flex-auto.flex-wrap{ data: { "multiselect-target" => "showSelectedArea" } }
|
|
9
|
-
.text-gray-300.w-8.py-1.pl-2.pr-1.border-l.flex.items-center.border-gray-200
|
|
9
|
+
.text-gray-300.w-8.py-1.pl-2.pr-1.border-l.flex.items-center.border-gray-200.dark:text-gray-500.dark:border-gray-600
|
|
10
10
|
^
|
|
@@ -45,7 +45,14 @@ module Tailwinds
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def select_as_input
|
|
48
|
-
render(
|
|
48
|
+
render(
|
|
49
|
+
Tailwinds::Form::Multiselect::SelectAsInput.new(
|
|
50
|
+
options:,
|
|
51
|
+
attribute:,
|
|
52
|
+
input:,
|
|
53
|
+
size_class: size_class(:multiselect_input)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
49
56
|
end
|
|
50
57
|
|
|
51
58
|
def on_change
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
.mb-4
|
|
2
|
+
- if @label
|
|
3
|
+
= component('tailwinds/form/label', for: @for) do
|
|
4
|
+
= @label
|
|
5
|
+
- base_classes = 'w-full bg-white border border-gray-300 rounded focus:outline-none focus:border-red-500 '
|
|
6
|
+
- base_classes += 'dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:border-red-400 dark:placeholder-white'
|
|
7
|
+
- classes = "#{size_class(:text_input)} #{base_classes}"
|
|
8
|
+
= @input.call @attribute, **@options.merge(class: classes), value: @value
|
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
- if @label
|
|
3
3
|
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
|
-
|
|
5
|
+
- base_classes = 'bg-white border border-gray-300 text-gray-700 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 '
|
|
6
|
+
- base_classes += 'focus:border-transparent disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed '
|
|
7
|
+
- base_classes += 'dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100 dark:focus:ring-red-400 dark:disabled:bg-gray-800 dark:disabled:text-gray-500'
|
|
8
|
+
- classes = "#{size_class(:select_input)} #{base_classes}"
|
|
9
|
+
= @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: classes))
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
.flex.items-center.justify-between
|
|
2
|
-
|
|
2
|
+
- base_classes = 'bg-red-500 hover:bg-red-700 text-white font-bold rounded focus:outline-none focus:shadow-outline cursor-pointer '
|
|
3
|
+
- base_classes += 'dark:text-white dark:bg-red-600 dark:hover:bg-red-500 dark:focus:ring-2 dark:focus:ring-red-400'
|
|
4
|
+
- classes = "#{size_class(:submit_button)} #{base_classes}"
|
|
5
|
+
%button{ type: :submit, name: :commit, class: classes, **@options }
|
|
3
6
|
= @text
|
|
4
7
|
= @content
|
|
@@ -4,10 +4,18 @@ module Tailwinds
|
|
|
4
4
|
module Form
|
|
5
5
|
# Tailwind-styled submit button
|
|
6
6
|
class SubmitButtonComponent < TailwindComponent
|
|
7
|
-
def initialize(action, **options)
|
|
8
|
-
@options = options.except :type
|
|
9
|
-
|
|
7
|
+
def initialize(action, size: :middle, **options)
|
|
10
8
|
@text = action.is_a?(String) ? action : action.to_s.capitalize
|
|
9
|
+
|
|
10
|
+
super(
|
|
11
|
+
input: nil,
|
|
12
|
+
attribute: nil,
|
|
13
|
+
value: nil,
|
|
14
|
+
options: options.except(:type),
|
|
15
|
+
label: nil,
|
|
16
|
+
for: nil,
|
|
17
|
+
size:
|
|
18
|
+
)
|
|
11
19
|
end
|
|
12
20
|
end
|
|
13
21
|
end
|
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
- if @label
|
|
3
3
|
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
|
-
|
|
5
|
+
- base_classes = 'w-full bg-white border border-gray-300 rounded focus:outline-none focus:border-red-500 '
|
|
6
|
+
- base_classes += 'dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:border-red-400 dark:placeholder-white'
|
|
7
|
+
- classes = "#{size_class(:text_input)} #{base_classes}"
|
|
8
|
+
= @input.call @attribute, **@options.merge(class: classes), value: @value
|
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
- if @label
|
|
3
3
|
= component('tailwinds/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
|
-
|
|
5
|
+
- base_classes = 'w-full bg-white border border-gray-300 rounded focus:outline-none focus:border-red-500 '
|
|
6
|
+
- base_classes += 'dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:border-red-400 dark:placeholder-white'
|
|
7
|
+
- classes = "#{size_class(:text_input)} #{base_classes}"
|
|
8
|
+
= @input.call @attribute, **@options.merge(class: classes), value: @value
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.div-table-row.grid.text-white.text-small.gap-4.bg-purple-700.dark:bg-gray-700.dark:text-gray-400{ class: "grid-cols-#{
|
|
1
|
+
.div-table-row.grid.text-white.text-small.gap-4.bg-purple-700.dark:bg-gray-700.dark:text-gray-400{ class: "grid-cols-#{columns_count}", aria: { label: "Table Header" }, role: "row" }
|
|
2
2
|
- if headers.any?
|
|
3
3
|
- headers.each do |header|
|
|
4
4
|
.div-table-cell.py-4.px-6
|
|
@@ -4,7 +4,12 @@ module Tailwinds
|
|
|
4
4
|
module Table
|
|
5
5
|
# Component for rendering a header in a table
|
|
6
6
|
class HeaderComponent < Tramway::Component::Base
|
|
7
|
-
option :headers
|
|
7
|
+
option :headers, optional: true, default: -> { [] }
|
|
8
|
+
option :columns, optional: true, default: -> { 3 }
|
|
9
|
+
|
|
10
|
+
def columns_count
|
|
11
|
+
headers.present? ? headers.size : columns
|
|
12
|
+
end
|
|
8
13
|
end
|
|
9
14
|
end
|
|
10
15
|
end
|
|
@@ -12,26 +12,22 @@ module Tailwinds
|
|
|
12
12
|
default_attributes = { role: :row }
|
|
13
13
|
|
|
14
14
|
if href.present?
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
link_to(href, options.merge(class: klass, **default_attributes)) do
|
|
15
|
+
link_to(href, options.merge(class: "#{options[:class] || ''} #{link_row_classes}", **default_attributes)) do
|
|
18
16
|
yield if block_given?
|
|
19
17
|
end
|
|
20
18
|
else
|
|
21
|
-
tag.div(**options
|
|
19
|
+
tag.div(**options, **default_attributes) do
|
|
22
20
|
yield if block_given?
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
# :reek:UtilityFunction { enabled: false }
|
|
28
25
|
def desktop_row_classes(cells_count)
|
|
29
26
|
[
|
|
30
27
|
'div-table-row', 'grid', 'gap-4', 'bg-white', 'border-b', 'last:border-b-0', 'dark:bg-gray-800',
|
|
31
28
|
'dark:border-gray-700', "grid-cols-#{cells_count}"
|
|
32
29
|
].join(' ')
|
|
33
30
|
end
|
|
34
|
-
# :reek:UtilityFunction { enabled: true }
|
|
35
31
|
|
|
36
32
|
def link_row_classes
|
|
37
33
|
'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
= helpers.component 'tailwinds/table/row/preview'
|
|
2
2
|
|
|
3
3
|
- width_class = options[:class]&.include?('w-') ? '' : 'w-full'
|
|
4
|
-
.div-table.text-left.rtl:text-right.text-gray-500.dark:text-gray-400
|
|
4
|
+
.div-table.text-left.rtl:text-right.text-gray-500.dark:text-gray-400{ class: "#{options[:class]} #{width_class}", **options.except(:class) }
|
|
5
5
|
= content
|
|
@@ -11,11 +11,13 @@ module Tramway
|
|
|
11
11
|
include Rails.application.routes.url_helpers
|
|
12
12
|
|
|
13
13
|
def index
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
if index_scope.present?
|
|
15
|
+
model_class.public_send(index_scope)
|
|
16
|
+
else
|
|
17
|
+
model_class.order(id: :desc)
|
|
18
|
+
end.page(params[:page]) => entities
|
|
19
|
+
|
|
20
|
+
@entities = entities
|
|
19
21
|
|
|
20
22
|
@namespace = entity.route&.namespace
|
|
21
23
|
end
|
|
@@ -29,5 +31,9 @@ module Tramway
|
|
|
29
31
|
def entity
|
|
30
32
|
@entity ||= Tramway.config.entities.find { |e| e.name == params[:entity][:name] }
|
|
31
33
|
end
|
|
34
|
+
|
|
35
|
+
def index_scope
|
|
36
|
+
entity.page(:index).scope
|
|
37
|
+
end
|
|
32
38
|
end
|
|
33
39
|
end
|
data/config/routes.rb
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Tramway::Engine.routes.draw do
|
|
4
4
|
Tramway.config.entities.each do |entity|
|
|
5
5
|
segments = entity.name.split('/')
|
|
6
|
-
resource_name = segments.pop
|
|
6
|
+
resource_name = segments.pop
|
|
7
7
|
|
|
8
8
|
define_resource = proc do
|
|
9
9
|
resources resource_name.pluralize.to_sym,
|
|
10
|
-
only:
|
|
11
|
-
controller:'/tramway/entities',
|
|
12
|
-
defaults:
|
|
10
|
+
only: [:index],
|
|
11
|
+
controller: '/tramway/entities',
|
|
12
|
+
defaults: { entity: entity }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
if segments.empty?
|
data/lib/tramway/base_form.rb
CHANGED
|
@@ -20,26 +20,19 @@ module Tramway
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def decorator_class_name(object_or_array_or_class, namespace)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
if Tramway::Decorators::CollectionDecorators.collection?(object_or_array_or_class)
|
|
24
|
+
object_or_array_or_class.first.class
|
|
25
|
+
elsif object_or_array_or_class.is_a?(Class)
|
|
26
|
+
object_or_array_or_class
|
|
27
|
+
else
|
|
28
|
+
object_or_array_or_class.class
|
|
29
|
+
end => klass
|
|
30
30
|
|
|
31
31
|
base_class_name = Tramway::Decorators::NameBuilder.default_decorator_class_name(klass)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if klass_name.safe_constantize
|
|
36
|
-
klass_name
|
|
37
|
-
else
|
|
38
|
-
raise NameError, "You should define #{klass_name} decorator class in app/decorators/ folder."
|
|
39
|
-
end
|
|
33
|
+
build_klass_name(base_class_name, namespace)
|
|
40
34
|
end
|
|
41
35
|
|
|
42
|
-
# :reek:NilCheck { enabled: false }
|
|
43
36
|
def raise_error_if_object_empty(object_or_array, decorator)
|
|
44
37
|
return unless object_or_array.blank? && decorator.nil?
|
|
45
38
|
|
|
@@ -47,7 +40,16 @@ module Tramway
|
|
|
47
40
|
|
|
48
41
|
raise ArgumentError, text
|
|
49
42
|
end
|
|
50
|
-
|
|
43
|
+
|
|
44
|
+
def build_klass_name(base_class_name, namespace)
|
|
45
|
+
klass_name = namespace.present? ? "#{namespace.to_s.camelize}::#{base_class_name}" : base_class_name
|
|
46
|
+
|
|
47
|
+
unless klass_name.safe_constantize
|
|
48
|
+
raise NameError, "You should define #{klass_name} decorator class in app/decorators/ folder."
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
klass_name
|
|
52
|
+
end
|
|
51
53
|
end
|
|
52
54
|
end
|
|
53
55
|
end
|
|
@@ -10,21 +10,17 @@ module Tramway
|
|
|
10
10
|
@properties << attribute
|
|
11
11
|
|
|
12
12
|
define_method(attribute) do
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
raise NoMethodError, "#{self.class}##{attribute} is not defined"
|
|
17
|
-
end
|
|
13
|
+
raise NoMethodError, "#{self.class}##{attribute} is not defined" unless object.respond_to?(attribute)
|
|
14
|
+
|
|
15
|
+
object.public_send(attribute)
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
set_method = "#{attribute}="
|
|
21
19
|
|
|
22
20
|
define_method(set_method) do |value|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
raise NoMethodError, "#{self.class}##{set_method} is not defined"
|
|
27
|
-
end
|
|
21
|
+
raise NoMethodError, "#{self.class}##{set_method} is not defined" unless object.respond_to?(set_method)
|
|
22
|
+
|
|
23
|
+
object.public_send(set_method, value)
|
|
28
24
|
end
|
|
29
25
|
end
|
|
30
26
|
|
|
@@ -4,8 +4,13 @@ module Tramway
|
|
|
4
4
|
module Helpers
|
|
5
5
|
# Provides view-oriented helpers for ActionView
|
|
6
6
|
module ViewsHelper
|
|
7
|
-
def tramway_form_for(object, *, **options, &)
|
|
8
|
-
form_for(
|
|
7
|
+
def tramway_form_for(object, *, size: :middle, **options, &)
|
|
8
|
+
form_for(
|
|
9
|
+
object,
|
|
10
|
+
*,
|
|
11
|
+
**options.merge(builder: Tailwinds::Form::Builder, size:),
|
|
12
|
+
&
|
|
13
|
+
)
|
|
9
14
|
end
|
|
10
15
|
end
|
|
11
16
|
end
|
data/lib/tramway/navbar.rb
CHANGED
|
@@ -10,13 +10,13 @@ module Tramway
|
|
|
10
10
|
@items = { left: [], right: [] }
|
|
11
11
|
@filling = nil
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
entities = Tramway.config.entities
|
|
13
|
+
return unless with_entities
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
entities = Tramway.config.entities
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
return unless entities.any?
|
|
18
|
+
|
|
19
|
+
preset_left entities
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def left
|
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.5.
|
|
4
|
+
version: 0.5.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kalashnikovisme
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2025-
|
|
12
|
+
date: 2025-10-23 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: anyway_config
|
|
@@ -158,6 +158,8 @@ files:
|
|
|
158
158
|
- app/components/tailwinds/form/multiselect/selected_item_template.rb
|
|
159
159
|
- app/components/tailwinds/form/multiselect_component.html.haml
|
|
160
160
|
- app/components/tailwinds/form/multiselect_component.rb
|
|
161
|
+
- app/components/tailwinds/form/number_field_component.html.haml
|
|
162
|
+
- app/components/tailwinds/form/number_field_component.rb
|
|
161
163
|
- app/components/tailwinds/form/select_component.html.haml
|
|
162
164
|
- app/components/tailwinds/form/select_component.rb
|
|
163
165
|
- app/components/tailwinds/form/submit_button_component.html.haml
|