tramway 2.2.7 → 2.3.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 +54 -4
- data/app/components/{tailwinds → tramway}/back_button_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/badge_component.rb +4 -2
- data/app/components/{tailwinds → tramway}/button_component.rb +3 -1
- data/app/components/tramway/chat_component.html.haml +77 -0
- data/app/components/tramway/chat_component.rb +17 -0
- data/app/components/tramway/chats/message_component.html.haml +26 -0
- data/app/components/tramway/chats/message_component.rb +58 -0
- data/app/components/tramway/chats/messages/container_component.html.haml +9 -0
- data/app/components/tramway/chats/messages/container_component.rb +32 -0
- data/app/components/tramway/chats/messages/table_component.html.haml +28 -0
- data/app/components/tramway/chats/messages/table_component.rb +12 -0
- data/app/components/tramway/chats.rb +7 -0
- data/app/components/{tailwinds/base_component.rb → tramway/colors_methods.rb} +3 -3
- data/app/components/{tailwinds → tramway}/containers/main_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/containers/narrow_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/flash_component.rb +5 -3
- data/app/components/{tailwinds → tramway}/form/builder.rb +8 -7
- data/app/components/{tailwinds → tramway}/form/checkbox_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/checkbox_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/date_field_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/date_field_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/datetime_field_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/datetime_field_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/file_field_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/label_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/multiselect/caret_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/multiselect/dropdown_container_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/multiselect/item_container_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/multiselect/select_as_input_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/multiselect/selected_item_template_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/multiselect_component.html.haml +3 -3
- data/app/components/{tailwinds → tramway}/form/multiselect_component.rb +5 -5
- data/app/components/{tailwinds → tramway}/form/number_field_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/number_field_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/select_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/select_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/text_area_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/text_area_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/form/text_field_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/form/text_field_component.rb +1 -1
- data/app/components/tramway/native_text_component.html.haml +2 -0
- data/app/components/tramway/native_text_component.rb +148 -0
- data/app/components/{tailwinds → tramway}/nav/item/button_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/nav/item/link_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/nav/item_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/navbar_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/pagination/base.rb +1 -1
- data/app/components/{tailwinds → tramway}/pagination/first_page_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/pagination/gap_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/pagination/last_page_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/pagination/next_page_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/pagination/page_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/pagination/prev_page_component.rb +2 -2
- data/app/components/{tailwinds → tramway}/table/cell_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/table/header_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/table/row/preview_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/table/row_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/table_component.html.haml +1 -1
- data/app/components/{tailwinds → tramway}/table_component.rb +1 -1
- data/app/components/{tailwinds → tramway}/title_component.rb +1 -1
- data/app/views/kaminari/_first_page.html.haml +1 -1
- data/app/views/kaminari/_gap.html.haml +1 -1
- data/app/views/kaminari/_last_page.html.haml +1 -1
- data/app/views/kaminari/_next_page.html.haml +1 -1
- data/app/views/kaminari/_page.html.haml +1 -1
- data/app/views/kaminari/_prev_page.html.haml +1 -1
- data/app/views/tramway/chats/_message.html.haml +4 -0
- data/config/locales/en.yml +4 -0
- data/config/tailwind.config.js +81 -1
- data/docs/AGENTS.md +21 -0
- data/lib/tramway/chats/broadcast.rb +19 -0
- data/lib/tramway/engine.rb +11 -0
- data/lib/tramway/helpers/navbar_helper.rb +1 -1
- data/lib/tramway/helpers/views_helper.rb +27 -15
- data/lib/tramway/navbar.rb +4 -4
- data/lib/tramway/version.rb +1 -1
- data/lib/tramway.rb +1 -0
- metadata +90 -77
- /data/app/components/{tailwinds → tramway}/back_button_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/badge_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/button_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/containers/main_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/containers/narrow_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/flash_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/file_field_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/label_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/multiselect/caret_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/multiselect/dropdown_container_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/multiselect/item_container_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/multiselect/select_as_input_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/form/multiselect/selected_item_template_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/nav/item/button_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/nav/item/link_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/navbar_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/pagination/first_page_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/pagination/gap_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/pagination/last_page_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/pagination/next_page_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/pagination/page_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/pagination/prev_page_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/table/cell_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/table/header_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/table/row/preview_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/table/row_component.html.haml +0 -0
- /data/app/components/{tailwinds → tramway}/title_component.html.haml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.mb-4.relative
|
|
2
2
|
- if @label
|
|
3
|
-
= component('
|
|
3
|
+
= component('tramway/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
%div{ role: :combobox, data: multiselect_hash, id: "#{@for}_multiselect" }
|
|
6
6
|
- classes = "#{size_class(:multiselect_input)} #{select_base_classes}"
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
.flex.flex-row.flex-nowrap.overflow-x-auto.space-x-1{ data: { "multiselect-target" => "showSelectedArea" } }
|
|
9
9
|
.flex.flex-col.justify-center
|
|
10
10
|
.caret-down{ data: { "multiselect-target" => "caretDown" } }
|
|
11
|
-
= component '
|
|
11
|
+
= component 'tramway/form/multiselect/caret', size: size, direction: :down
|
|
12
12
|
.caret-up.hidden{ data: { "multiselect-target" => "caretUp" } }
|
|
13
|
-
= component '
|
|
13
|
+
= component 'tramway/form/multiselect/caret', size: size, direction: :up
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
module Form
|
|
5
5
|
# Tailwind-styled multi-select field
|
|
6
6
|
class MultiselectComponent < TailwindComponent
|
|
@@ -73,7 +73,7 @@ module Tailwinds
|
|
|
73
73
|
|
|
74
74
|
def select_as_input
|
|
75
75
|
component(
|
|
76
|
-
'
|
|
76
|
+
'tramway/form/multiselect/select_as_input',
|
|
77
77
|
options:,
|
|
78
78
|
attribute:,
|
|
79
79
|
input:,
|
|
@@ -96,15 +96,15 @@ module Tailwinds
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def selected_item_template
|
|
99
|
-
component('
|
|
99
|
+
component('tramway/form/multiselect/selected_item_template', size:)
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def dropdown_container
|
|
103
|
-
component('
|
|
103
|
+
component('tramway/form/multiselect/dropdown_container', size:)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def item_container
|
|
107
|
-
component('
|
|
107
|
+
component('tramway/form/multiselect/item_container', size:)
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
= component('
|
|
3
|
+
= component('tramway/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
- classes = "#{size_class(:text_input)} #{text_input_base_classes}"
|
|
6
6
|
= @input.call @attribute, **@options.merge(class: classes), value: @value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
= component('
|
|
3
|
+
= component('tramway/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
- classes = "#{size_class(:select_input)} #{select_base_classes}"
|
|
6
6
|
= @input.call(@attribute, @collection, { selected: @value }, @options.merge(class: classes))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
= component('
|
|
3
|
+
= component('tramway/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
- classes = "#{size_class(:text_input)} #{text_input_base_classes}"
|
|
6
6
|
= @input.call @attribute, **@options.merge(class: classes), value: @value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.mb-4
|
|
2
2
|
- if @label
|
|
3
|
-
= component('
|
|
3
|
+
= component('tramway/form/label', for: @for) do
|
|
4
4
|
= @label
|
|
5
5
|
- classes = "#{size_class(:text_input)} #{text_input_base_classes}"
|
|
6
6
|
= @input.call @attribute, **@options.merge(class: classes), value: @value
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
|
|
5
|
+
module Tramway
|
|
6
|
+
# Displays text with size-based utility classes.
|
|
7
|
+
class NativeTextComponent < Tramway::BaseComponent
|
|
8
|
+
URL_REGEX = %r{https?://[^\s<]+}
|
|
9
|
+
MAX_URL_LENGTH = 41
|
|
10
|
+
HEADER_REGEX = /\A(#{Regexp.escape('#')}{1,6})\s+(.+)\z/
|
|
11
|
+
LIST_ITEM_REGEX = /\A[-*]\s+(.+)\z/
|
|
12
|
+
HEADER_CLASSES = {
|
|
13
|
+
1 => 'text-4xl font-bold leading-tight mt-4 mb-2',
|
|
14
|
+
2 => 'text-3xl font-bold leading-tight mt-4 mb-2',
|
|
15
|
+
3 => 'text-2xl font-semibold leading-snug mt-3 mb-2',
|
|
16
|
+
4 => 'text-xl font-semibold leading-snug mt-3 mb-2',
|
|
17
|
+
5 => 'text-lg font-semibold leading-snug mt-2 mb-1',
|
|
18
|
+
6 => 'text-base font-semibold leading-snug mt-2 mb-1'
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
option :text
|
|
22
|
+
option :size, optional: true, default: -> { :middle }
|
|
23
|
+
option :klass, optional: true, default: -> { '' }
|
|
24
|
+
|
|
25
|
+
def rendered_html
|
|
26
|
+
# Safe because this is an empty static string literal, not user-controlled content.
|
|
27
|
+
return ''.html_safe if text.blank?
|
|
28
|
+
|
|
29
|
+
helpers.safe_join(rendered_blocks)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def text_class
|
|
33
|
+
{ small: 'text-sm', large: 'text-lg', middle: 'text-base' }.fetch(size, 'md:text-sm lg:text-base') + " #{klass}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# rubocop:disable Metrics/AbcSize
|
|
39
|
+
# rubocop:disable Metrics/MethodLength
|
|
40
|
+
def rendered_blocks
|
|
41
|
+
blocks = []
|
|
42
|
+
list_items = []
|
|
43
|
+
|
|
44
|
+
text.split("\n").each do |line|
|
|
45
|
+
stripped_line = line.strip
|
|
46
|
+
|
|
47
|
+
if stripped_line.blank?
|
|
48
|
+
flush_list_items(blocks, list_items)
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
header_match = stripped_line.match(HEADER_REGEX)
|
|
53
|
+
if header_match
|
|
54
|
+
flush_list_items(blocks, list_items)
|
|
55
|
+
blocks << helpers.content_tag(
|
|
56
|
+
"h#{header_match[1].length}",
|
|
57
|
+
render_inline_markdown(header_match[2]),
|
|
58
|
+
class: header_class(header_match[1].length)
|
|
59
|
+
)
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
list_match = stripped_line.match(LIST_ITEM_REGEX)
|
|
64
|
+
if list_match
|
|
65
|
+
list_items << helpers.content_tag(:li, render_inline_markdown(list_match[1]),
|
|
66
|
+
class: "#{text_class} marker:hidden")
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
flush_list_items(blocks, list_items)
|
|
71
|
+
blocks << helpers.content_tag(:p, render_inline_markdown(stripped_line), class: "#{text_class} my-1")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
flush_list_items(blocks, list_items)
|
|
75
|
+
blocks
|
|
76
|
+
end
|
|
77
|
+
# rubocop:enable Metrics/AbcSize
|
|
78
|
+
# rubocop:enable Metrics/MethodLength
|
|
79
|
+
|
|
80
|
+
def flush_list_items(blocks, list_items)
|
|
81
|
+
return if list_items.empty?
|
|
82
|
+
|
|
83
|
+
blocks << helpers.content_tag(:ul, helpers.safe_join(list_items), class: "list-none pl-5 my-2 #{klass}")
|
|
84
|
+
list_items.clear
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def header_class(level)
|
|
88
|
+
"#{HEADER_CLASSES.fetch(level)} #{klass}".strip
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_inline_markdown(content)
|
|
92
|
+
escaped_content = ERB::Util.html_escape(content)
|
|
93
|
+
with_bold = escaped_content.gsub(/\*\*(.+?)\*\*/, '<strong>\\1</strong>')
|
|
94
|
+
with_italics = with_bold.gsub(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/, '<em>\\1</em>')
|
|
95
|
+
with_underscored_italics = with_italics.gsub(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/, '<em>\\1</em>')
|
|
96
|
+
# Safe because user input has already been escaped above and only controlled tags are introduced.
|
|
97
|
+
# rubocop:disable Rails/OutputSafety
|
|
98
|
+
linkified(with_underscored_italics).html_safe
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# rubocop:disable Metrics/MethodLength
|
|
102
|
+
# rubocop:disable Metrics/AbcSize
|
|
103
|
+
def linkified(content)
|
|
104
|
+
fragments = []
|
|
105
|
+
current_index = 0
|
|
106
|
+
|
|
107
|
+
content.to_enum(:scan, URL_REGEX).map do
|
|
108
|
+
match = Regexp.last_match
|
|
109
|
+
matched_url = match[0]
|
|
110
|
+
url, trailing = strip_trailing_punctuation(matched_url)
|
|
111
|
+
|
|
112
|
+
# Safe because this fragment is sliced from `content`, which was already HTML-escaped.
|
|
113
|
+
|
|
114
|
+
fragments << content[current_index...match.begin(0)].html_safe # rubocop:disable Rails/OutputSafety
|
|
115
|
+
fragments << helpers.link_to(
|
|
116
|
+
shorten(url),
|
|
117
|
+
url,
|
|
118
|
+
target: '_blank',
|
|
119
|
+
rel: 'noopener noreferrer',
|
|
120
|
+
class: 'text-blue-400 hover:underline'
|
|
121
|
+
)
|
|
122
|
+
# Safe because `trailing` can only contain stripped URL punctuation (.,!?;:).
|
|
123
|
+
fragments << trailing.html_safe if trailing.present? # rubocop:disable Rails/OutputSafety
|
|
124
|
+
current_index = match.end(0)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Safe because this tail fragment also comes from the already escaped `content` string.
|
|
128
|
+
fragments << content[current_index..].html_safe if current_index < content.length
|
|
129
|
+
# rubocop:enable Rails/OutputSafety
|
|
130
|
+
|
|
131
|
+
helpers.safe_join(fragments)
|
|
132
|
+
end
|
|
133
|
+
# rubocop:enable Metrics/AbcSize
|
|
134
|
+
# rubocop:enable Metrics/MethodLength
|
|
135
|
+
|
|
136
|
+
def strip_trailing_punctuation(url)
|
|
137
|
+
stripped_url = url.sub(/[.,!?;:]+\z/, '')
|
|
138
|
+
trailing = url.delete_prefix(stripped_url)
|
|
139
|
+
[stripped_url, trailing]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def shorten(url)
|
|
143
|
+
return url if url.length <= MAX_URL_LENGTH
|
|
144
|
+
|
|
145
|
+
"#{url[0...MAX_URL_LENGTH]}..."
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rules/turbo_html_attributes_rules'
|
|
4
4
|
|
|
5
|
-
module
|
|
5
|
+
module Tramway
|
|
6
6
|
module Nav
|
|
7
7
|
module Item
|
|
8
8
|
# Render button styled with Tailwind using button_to methods
|
|
9
9
|
#
|
|
10
|
-
class ButtonComponent <
|
|
10
|
+
class ButtonComponent < Tramway::Nav::ItemComponent
|
|
11
11
|
def initialize(**options)
|
|
12
12
|
@href = options[:href]
|
|
13
13
|
@method = options[:method]
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require 'rules/turbo_html_attributes_rules'
|
|
4
4
|
|
|
5
|
-
module
|
|
5
|
+
module Tramway
|
|
6
6
|
module Nav
|
|
7
7
|
module Item
|
|
8
8
|
# Render button styled with Tailwind using link_to methods
|
|
9
9
|
#
|
|
10
|
-
class LinkComponent <
|
|
10
|
+
class LinkComponent < Tramway::Nav::ItemComponent
|
|
11
11
|
def initialize(**options)
|
|
12
12
|
@href = options[:href]
|
|
13
13
|
@options = Rules::TurboHtmlAttributesRules.prepare_turbo_html_attributes(options:)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
module Pagination
|
|
5
5
|
# Kaminari first page component for rendering a first page button in a pagination
|
|
6
|
-
class FirstPageComponent <
|
|
6
|
+
class FirstPageComponent < Tramway::Pagination::Base
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
module Pagination
|
|
5
5
|
# Kaminari next page component for rendering a last page button in a pagination
|
|
6
|
-
class LastPageComponent <
|
|
6
|
+
class LastPageComponent < Tramway::Pagination::Base
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
module Pagination
|
|
5
5
|
# Kaminari next page component for rendering a next page button in a pagination
|
|
6
|
-
class NextPageComponent <
|
|
6
|
+
class NextPageComponent < Tramway::Pagination::Base
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
module Pagination
|
|
5
5
|
# Kaminari page component for rendering a page button in a pagination
|
|
6
|
-
class PageComponent <
|
|
6
|
+
class PageComponent < Tramway::Pagination::Base
|
|
7
7
|
option :page
|
|
8
8
|
|
|
9
9
|
def current_page_classes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
module Pagination
|
|
5
5
|
# Kaminari prev page component for rendering a prev page button in a pagination
|
|
6
|
-
class PrevPageComponent <
|
|
6
|
+
class PrevPageComponent < Tramway::Pagination::Base
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= component '
|
|
1
|
+
= component 'tramway/pagination/first_page', current_page:, url:, remote:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= component '
|
|
1
|
+
= component 'tramway/pagination/gap'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= component '
|
|
1
|
+
= component 'tramway/pagination/last_page', current_page:, url:, remote:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= component '
|
|
1
|
+
= component 'tramway/pagination/next_page', current_page:, url:, remote:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= component '
|
|
1
|
+
= component 'tramway/pagination/page', current_page:, url:, remote:, page:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
= component '
|
|
1
|
+
= component 'tramway/pagination/prev_page', current_page:, url:, remote:
|
data/config/locales/en.yml
CHANGED
data/config/tailwind.config.js
CHANGED
|
@@ -27,7 +27,87 @@ module.exports = {
|
|
|
27
27
|
|
|
28
28
|
// === Entities Index Page ===
|
|
29
29
|
'md:mt-8',
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
// === Chat components ===
|
|
32
|
+
'mt-6',
|
|
33
|
+
'mt-3',
|
|
34
|
+
'max-h-full',
|
|
35
|
+
'rounded-2xl',
|
|
36
|
+
'rounded-tl-md',
|
|
37
|
+
'rounded-tr-md',
|
|
38
|
+
'bg-white',
|
|
39
|
+
'bg-gray-50',
|
|
40
|
+
'bg-blue-600',
|
|
41
|
+
'shadow-sm',
|
|
42
|
+
'border-t',
|
|
43
|
+
'border-gray-700',
|
|
44
|
+
'bg-gray-800',
|
|
45
|
+
'bg-gray-800/60',
|
|
46
|
+
'bg-gray-900',
|
|
47
|
+
'bg-blue-500',
|
|
48
|
+
'hover:bg-blue-400',
|
|
49
|
+
'text-gray-100',
|
|
50
|
+
'text-gray-400',
|
|
51
|
+
'text-blue-400',
|
|
52
|
+
'placeholder:text-gray-400',
|
|
53
|
+
'focus:border-blue-400',
|
|
54
|
+
'focus:ring-blue-500/30',
|
|
55
|
+
'ring-gray-700',
|
|
56
|
+
'h-8',
|
|
57
|
+
'flex-1',
|
|
58
|
+
'gap-2',
|
|
59
|
+
'gap-3',
|
|
60
|
+
'items-start',
|
|
61
|
+
'items-end',
|
|
62
|
+
'max-w-lg',
|
|
63
|
+
'overflow-hidden',
|
|
64
|
+
'overflow-y-auto',
|
|
65
|
+
'my-4',
|
|
66
|
+
'p-6',
|
|
67
|
+
'pb-1',
|
|
68
|
+
'py-3',
|
|
69
|
+
'ring-1',
|
|
70
|
+
'space-y-4',
|
|
71
|
+
'text-right',
|
|
72
|
+
'text-sm',
|
|
73
|
+
'text-gray-900',
|
|
74
|
+
'text-blue-100/90',
|
|
75
|
+
'text-red-200',
|
|
76
|
+
'underline',
|
|
77
|
+
'break-all',
|
|
78
|
+
'animate-spin',
|
|
79
|
+
'placeholder:text-gray-400',
|
|
80
|
+
'focus:border-blue-500',
|
|
81
|
+
'focus:outline-none',
|
|
82
|
+
'focus:ring-2',
|
|
83
|
+
'focus:ring-blue-200',
|
|
84
|
+
'disabled:bg-gray-100',
|
|
85
|
+
'hover:bg-blue-500',
|
|
86
|
+
'bg-blue-600',
|
|
87
|
+
'bg-gray-800/60',
|
|
88
|
+
'min-h-0',
|
|
89
|
+
'min-w-0',
|
|
90
|
+
'flex-1',
|
|
91
|
+
'overflow-y-auto',
|
|
92
|
+
'overflow-hidden',
|
|
93
|
+
'shrink-0',
|
|
94
|
+
'md:p-4',
|
|
95
|
+
'md:p-6',
|
|
96
|
+
'p-2',
|
|
97
|
+
'md:gap-1',
|
|
98
|
+
'md:space-y-4',
|
|
99
|
+
'space-y-2',
|
|
100
|
+
'md:pt-2',
|
|
101
|
+
'md:rounded-xl',
|
|
102
|
+
'rounded-t-xl',
|
|
103
|
+
'md:rounded-full',
|
|
104
|
+
'rounded-bl-2xl',
|
|
105
|
+
'md:border',
|
|
106
|
+
'md:gap-1',
|
|
107
|
+
'hover:underline',
|
|
108
|
+
'marker:hidden',
|
|
109
|
+
'list-none',
|
|
110
|
+
|
|
31
111
|
// === Custom table layout utilities ===
|
|
32
112
|
'div-table',
|
|
33
113
|
'div-table-row',
|
data/docs/AGENTS.md
CHANGED
|
@@ -127,6 +127,15 @@ Use `tramway_form_for` instead `form_with`, `form_for`
|
|
|
127
127
|
### Rule 8
|
|
128
128
|
Inherit all components from Tramway::BaseComponent
|
|
129
129
|
|
|
130
|
+
### Rule 8.1
|
|
131
|
+
When you need chat UI, use the `tramway_chat` helper. Pass `chat_id`, `messages`, `message_form`, and `send_message_path`.
|
|
132
|
+
Each message must include `:id` and a `:type` of `:sent` or `:received`, and other keys (like `:text`, `:data`, `:sent_at`)
|
|
133
|
+
are forwarded to `tramway/chats/message_component`. Use `message_form: nil` when you only need read-only chat rendering.
|
|
134
|
+
|
|
135
|
+
For live updates to a rendered `tramway_chat`, use `tramway_chat_append_message(chat_id:, message_type:, text:, sent_at:)`.
|
|
136
|
+
This method is included in all controllers and ActiveRecord models. `message_type` must be `:sent` or `:received`, otherwise
|
|
137
|
+
it raises `ArgumentError`. `chat_id` must match the stream id used in `tramway_chat`.
|
|
138
|
+
|
|
130
139
|
### Rule 9
|
|
131
140
|
If page `create` or `update` is configured for an entity, use Tramway Form pattern for forms. Visible fields are configured via `form_fields` method.
|
|
132
141
|
|
|
@@ -443,6 +452,18 @@ scope :for_role, -> (role) { where role: role }
|
|
|
443
452
|
### Rule 28
|
|
444
453
|
In case, you need to make a one link in tramway_table on each row. Use `tramway_row href: your_link` instead of putting the like inside a cell.
|
|
445
454
|
|
|
455
|
+
### Rule 29
|
|
456
|
+
Always use tramway decorated objects in views.
|
|
457
|
+
|
|
458
|
+
### Rule 30
|
|
459
|
+
For Tailwind classes with `/`, `[`, `]` characters use `{ class: 'here is the complicated class' }` in HAML.
|
|
460
|
+
|
|
461
|
+
### Rule 31
|
|
462
|
+
Always `tramway_decorate` and `tramway_form` for creating these types of objects. Don't use decorator and form classes for this.
|
|
463
|
+
|
|
464
|
+
### Rule 32
|
|
465
|
+
In Tramway Decorators, use `delegate_attributes` method instead of `delegate :something, to: :object`
|
|
466
|
+
|
|
446
467
|
## Controller Patterns
|
|
447
468
|
|
|
448
469
|
- Keep actions short and explicit with guard clauses.
|