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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0062ba6e33495a79ef0d2dc0fcca348e7de9ed5a892855d800bc7de45022205d
|
|
4
|
+
data.tar.gz: 350731c7514a5b6f3d513635c2397f65a52f81cb50a05ff0e198af191a09b75d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c5ad624bc5aa06c742ac6a02cbaea4accfe21028ec422f5818514a3bb87b854f06e830986f35d767a2fe82f06f0d3ab433e615651522e8ab332d46925e54c58
|
|
7
|
+
data.tar.gz: ffd97ea24ee55e590368a3a34beafcb440010358a65db27d0e231629225bcd05f0d0f22987c38f6dc7ebf97193ebc6f26ba76628280b2e1e92169f1b20dd69fa
|
data/README.md
CHANGED
|
@@ -17,6 +17,7 @@ that helps you generate good, Tramway-native code with all the framework feature
|
|
|
17
17
|
* [Tramway Form](https://github.com/Purple-Magic/tramway#tramway-form)
|
|
18
18
|
* [Tramway Navbar](https://github.com/Purple-Magic/tramway#tramway-navbar)
|
|
19
19
|
* [Tramway Flash](https://github.com/Purple-Magic/tramway#tramway-flash)
|
|
20
|
+
* [Tramway Chat](https://github.com/Purple-Magic/tramway#tramway-chat)
|
|
20
21
|
* [Tramway Table Component](https://github.com/Purple-Magic/tramway#tramway-table-component)
|
|
21
22
|
* [Tailwind-styled forms](https://github.com/Purple-Magic/tramway#tailwind-styled-forms)
|
|
22
23
|
* [Stimulus-based inputs](https://github.com/Purple-Magic/tramway#stimulus-based-inputs)
|
|
@@ -856,6 +857,55 @@ custom HTML options directly (e.g., `class:`, `data:`) and they will be merged i
|
|
|
856
857
|
Use the `type` argument is compatible to [Lantern Color Palette](https://github.com/TrinityMonsters/tramway/blob/main/README.md#lantern-color-palette) or provide a `color:` keyword to set
|
|
857
858
|
the Tailwind color family explicitly.
|
|
858
859
|
|
|
860
|
+
### Tramway Chat
|
|
861
|
+
|
|
862
|
+
`tramway_chat` renders the chat experience bundled with Tramway. Provide a chat ID, a list of message hashes, and the URL
|
|
863
|
+
that receives new messages. Each message must include an `:id` and a `:type` (either `:sent` or `:received`). Additional
|
|
864
|
+
message fields like `text`, `data`, or `sent_at` are forwarded to `tramway/chats/message_component`.
|
|
865
|
+
|
|
866
|
+
```haml
|
|
867
|
+
-# Haml example
|
|
868
|
+
- messages = [
|
|
869
|
+
- { id: 1, type: :received, text: 'Hello 👋' },
|
|
870
|
+
- { id: 2, type: :sent, text: 'Hi there!' }
|
|
871
|
+
- ]
|
|
872
|
+
= tramway_chat chat_id: 'support-chat',
|
|
873
|
+
messages: messages,
|
|
874
|
+
message_form: @message_form,
|
|
875
|
+
send_message_path: chat_messages_path
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
```erb
|
|
879
|
+
<%# ERB example %>
|
|
880
|
+
<% messages = [{ id: 1, type: :received, text: 'Hello 👋' }] %>
|
|
881
|
+
<%= tramway_chat chat_id: 'support-chat',
|
|
882
|
+
messages: messages,
|
|
883
|
+
message_form: @message_form,
|
|
884
|
+
send_message_path: chat_messages_path %>
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
If you do not want to render the message form, pass `message_form: nil`. When the form is present, `send_message_path` is
|
|
889
|
+
required and the helper will generate the correct POST form.
|
|
890
|
+
|
|
891
|
+
To append messages to an already-rendered `tramway_chat` stream, use `tramway_chat_append_message`.
|
|
892
|
+
The method is mixed into controllers and ActiveRecord models by Tramway and expects:
|
|
893
|
+
- `chat_id:` — the same value used in `tramway_chat chat_id:`
|
|
894
|
+
- `message_type:` — only `:sent` or `:received` (raises `ArgumentError` otherwise)
|
|
895
|
+
- `text:` — message content
|
|
896
|
+
- `sent_at:` — message timestamp
|
|
897
|
+
|
|
898
|
+
```ruby
|
|
899
|
+
tramway_chat_append_message(
|
|
900
|
+
chat_id: 'support-chat',
|
|
901
|
+
message_type: :received,
|
|
902
|
+
text: 'We got your request',
|
|
903
|
+
sent_at: Time.current
|
|
904
|
+
)
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
It broadcasts with `target: 'messages'` and renders the `tramway/chats/message` partial so the message appears in the live chat.
|
|
908
|
+
|
|
859
909
|
### Tramway Table Component
|
|
860
910
|
|
|
861
911
|
Tramway provides a responsive, tailwind-styled table with light and dark themes. Use the `tramway_table`, `tramway_row`, and
|
|
@@ -877,7 +927,7 @@ implementations.
|
|
|
877
927
|
<% end %>
|
|
878
928
|
```
|
|
879
929
|
|
|
880
|
-
`tramway_table` accepts the same optional `options` hash as `
|
|
930
|
+
`tramway_table` accepts the same optional `options` hash as `Tramway::TableComponent`. The hash is forwarded as HTML
|
|
881
931
|
attributes, so you can pass things like `id`, `data` attributes, or additional classes. If you do not supply your own width
|
|
882
932
|
utility (e.g. a class that starts with `w-`), the component automatically appends `w-full` to keep the table responsive. This
|
|
883
933
|
allows you to extend the default styling without losing the sensible defaults provided by the component.
|
|
@@ -901,12 +951,12 @@ Use the optional `href:` argument on `tramway_row` to turn an entire row into a
|
|
|
901
951
|
```
|
|
902
952
|
|
|
903
953
|
When you render a header you can either pass the `headers:` array, as in the examples above, or render custom header content in
|
|
904
|
-
the block. `
|
|
954
|
+
the block. `tramway_header` uses the length of the `headers` array to build the grid if the array is present.
|
|
905
955
|
If you omit the array and provide custom content, pass the `columns:` argument so the component knows how many grid columns to
|
|
906
956
|
generate.
|
|
907
957
|
|
|
908
958
|
```erb
|
|
909
|
-
<%=
|
|
959
|
+
<%= tramway_header columns: 4 do %>
|
|
910
960
|
<%= tramway_cell do %>
|
|
911
961
|
Custom header cell
|
|
912
962
|
<% end %>
|
|
@@ -1067,7 +1117,7 @@ Available form helpers:
|
|
|
1067
1117
|
|
|
1068
1118
|
*app/views/sessions/new.html.erb*
|
|
1069
1119
|
```erb
|
|
1070
|
-
<%= form_with url: login_path, scope: :session, local: true, builder:
|
|
1120
|
+
<%= form_with url: login_path, scope: :session, local: true, builder: Tramway::Form::Builder do |form| %>
|
|
1071
1121
|
<%= form.email_field :email %>
|
|
1072
1122
|
<%= form.password_field :password %>
|
|
1073
1123
|
<%= form.submit 'Log in' %>
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
# Default Tramway badge
|
|
5
5
|
#
|
|
6
|
-
class BadgeComponent <
|
|
6
|
+
class BadgeComponent < Tramway::BaseComponent
|
|
7
7
|
option :text
|
|
8
8
|
option :type, optional: true
|
|
9
9
|
option :color, optional: true
|
|
10
10
|
|
|
11
|
+
include Tramway::ColorsMethods
|
|
12
|
+
|
|
11
13
|
def classes
|
|
12
14
|
theme_classes(
|
|
13
15
|
classic: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Tramway
|
|
4
4
|
# Default Tramway button
|
|
5
5
|
#
|
|
6
6
|
class ButtonComponent < BaseComponent
|
|
@@ -14,6 +14,8 @@ module Tailwinds
|
|
|
14
14
|
option :options, optional: true, default: -> { {} }
|
|
15
15
|
option :form_options, optional: true, default: -> { {} }
|
|
16
16
|
|
|
17
|
+
include Tramway::ColorsMethods
|
|
18
|
+
|
|
17
19
|
def before_render
|
|
18
20
|
return if tag.present?
|
|
19
21
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
= helpers.turbo_stream_from chat_id, 'messages'
|
|
2
|
+
|
|
3
|
+
#chat.flex.flex-1.h-full.w-full.min-h-0.min-w-0.flex-col
|
|
4
|
+
#messages.flex.flex-col.flex-1.min-h-0.overflow-y-auto.p-2.md:p-6.space-y-2.md:space-y-4.md:rounded-xl.rounded-t-xl{ class: 'text-gray-100 bg-gray-800/60' }
|
|
5
|
+
- messages.each do |message|
|
|
6
|
+
= component "tramway/chats/message", **message
|
|
7
|
+
- if disabled?
|
|
8
|
+
= inline_svg 'icons/dots.svg', class: 'w-8 h-8 text-gray-500 mx-auto my-4'
|
|
9
|
+
|
|
10
|
+
- if message_form.present?
|
|
11
|
+
.shrink-0.border-gray-200.md:pt-2.border-gray-700
|
|
12
|
+
= form_for message_form, url: send_message_path, as: :message, method: :post, html: { class: 'flex items-center md:gap-1' } do |f|
|
|
13
|
+
- waiting_placeholder = t('tramway.chat.placeholders.waiting')
|
|
14
|
+
- typing_placeholder = t('tramway.chat.placeholders.type')
|
|
15
|
+
= f.text_field :text,
|
|
16
|
+
class: 'flex-1 md:rounded-full rounded-bl-2xl md:border border-gray-300 px-4 py-2 text-sm text-gray-900 shadow-sm placeholder:text-gray-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:cursor-not-allowed disabled:bg-gray-100 border-gray-600 bg-gray-800 text-white focus:border-blue-400 focus:ring-blue-500/30',
|
|
17
|
+
placeholder: send_messages_enabled ? typing_placeholder : waiting_placeholder,
|
|
18
|
+
disabled: !send_messages_enabled
|
|
19
|
+
|
|
20
|
+
= f.hidden_field :chat_id, value: chat_id
|
|
21
|
+
|
|
22
|
+
- options.each do |(key, value)|
|
|
23
|
+
= f.hidden_field key, value: value
|
|
24
|
+
|
|
25
|
+
= f.submit '🡩',
|
|
26
|
+
class: 'md:rounded-full bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200 bg-blue-500 hover:bg-blue-400 cursor-pointer'
|
|
27
|
+
|
|
28
|
+
:javascript
|
|
29
|
+
(() => {
|
|
30
|
+
const setupChatScroll = () => {
|
|
31
|
+
const messages = document.getElementById('messages');
|
|
32
|
+
if (!messages || messages.dataset.chatScrollInitialized === 'true') return;
|
|
33
|
+
|
|
34
|
+
const scrollToBottom = () => {
|
|
35
|
+
messages.scrollTop = messages.scrollHeight;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
scrollToBottom();
|
|
39
|
+
|
|
40
|
+
const observer = new MutationObserver(scrollToBottom);
|
|
41
|
+
observer.observe(messages, { childList: true });
|
|
42
|
+
|
|
43
|
+
messages.dataset.chatScrollInitialized = 'true';
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const setupChatInputClear = () => {
|
|
47
|
+
const forms = document.querySelectorAll('form[action][method]');
|
|
48
|
+
|
|
49
|
+
forms.forEach((form) => {
|
|
50
|
+
if (form.dataset.chatInputClearInitialized === 'true') return;
|
|
51
|
+
|
|
52
|
+
const textInput = form.querySelector('input[name="message[text]"]');
|
|
53
|
+
if (!textInput) return;
|
|
54
|
+
|
|
55
|
+
form.addEventListener('turbo:submit-end', (event) => {
|
|
56
|
+
if (event.detail.success) {
|
|
57
|
+
textInput.value = '';
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
form.dataset.chatInputClearInitialized = 'true';
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (document.readyState === 'loading') {
|
|
66
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
67
|
+
setupChatScroll();
|
|
68
|
+
setupChatInputClear();
|
|
69
|
+
}, { once: true });
|
|
70
|
+
} else {
|
|
71
|
+
setupChatScroll();
|
|
72
|
+
setupChatInputClear();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
document.addEventListener('turbo:load', setupChatScroll);
|
|
76
|
+
document.addEventListener('turbo:load', setupChatInputClear);
|
|
77
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
# Renders the chat container with messages and optional input form.
|
|
5
|
+
class ChatComponent < Tramway::BaseComponent
|
|
6
|
+
option :chat_id
|
|
7
|
+
option :messages
|
|
8
|
+
option :message_form, optional: true, default: -> {}
|
|
9
|
+
option :options, optional: true, default: -> { {} }
|
|
10
|
+
option :send_message_path
|
|
11
|
+
option :send_messages_enabled, optional: true, default: -> { true }
|
|
12
|
+
|
|
13
|
+
def disabled?
|
|
14
|
+
options[:disabled]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
- if on_the_left?
|
|
2
|
+
= component 'tramway/chats/messages/container',
|
|
3
|
+
position: :left,
|
|
4
|
+
text:,
|
|
5
|
+
sent_at:,
|
|
6
|
+
show_sent_at: show_sent_at? do
|
|
7
|
+
- if data_view == :table
|
|
8
|
+
= component 'tramway/chats/messages/table', data:
|
|
9
|
+
|
|
10
|
+
- if on_the_right?
|
|
11
|
+
= component 'tramway/chats/messages/container',
|
|
12
|
+
position: :right,
|
|
13
|
+
text:,
|
|
14
|
+
sent_at:,
|
|
15
|
+
show_sent_at: show_sent_at? do
|
|
16
|
+
|
|
17
|
+
- if pending?
|
|
18
|
+
.pb-1
|
|
19
|
+
= inline_svg 'icons/spinner.svg', class: 'w-4 h-4 text-white animate-spin'
|
|
20
|
+
|
|
21
|
+
- if failed?
|
|
22
|
+
.pb-1
|
|
23
|
+
= inline_svg 'icons/cross.svg', class: 'w-6 h-6 text-red-200'
|
|
24
|
+
|
|
25
|
+
- if data_view == :table
|
|
26
|
+
= component 'tramway/chats/messages/table', data:
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
module Chats
|
|
5
|
+
# Renders a single chat message with alignment and state styling.
|
|
6
|
+
class MessageComponent < Tramway::BaseComponent
|
|
7
|
+
option :type
|
|
8
|
+
option :text, optional: true, default: -> { '' }
|
|
9
|
+
option :data, optional: true, default: -> { {} }
|
|
10
|
+
option :sent_at, optional: true, default: -> {}
|
|
11
|
+
option :options, optional: true, default: -> { {} }
|
|
12
|
+
|
|
13
|
+
def data_view
|
|
14
|
+
if data.nil?
|
|
15
|
+
nil
|
|
16
|
+
elsif array_2d?(data)
|
|
17
|
+
:table
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def text_color
|
|
22
|
+
if pending?
|
|
23
|
+
'text-gray-400 dark:text-gray-600'
|
|
24
|
+
else
|
|
25
|
+
'text-black dark:text-white'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def array_2d?(array)
|
|
32
|
+
array.is_a?(Array) && array.all? do |inner|
|
|
33
|
+
inner.is_a?(Array) && inner.none? { |e| e.is_a?(Array) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def on_the_left?
|
|
38
|
+
type.to_sym == :received
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def on_the_right?
|
|
42
|
+
type.to_sym == :sent
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def show_sent_at?
|
|
46
|
+
sent_at.present?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def pending?
|
|
50
|
+
options[:pending]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def failed?
|
|
54
|
+
options[:failed]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
.flex.flex-col.gap-1{ class: position_classes }
|
|
2
|
+
- if text.present?
|
|
3
|
+
.max-w-lg.rounded-2xl.px-4.py-3.text-sm.shadow-sm.ring-1.text-white.ring-gray-700{ class: color_classes }
|
|
4
|
+
= component 'tramway/native_text', text:, klass: 'text-white'
|
|
5
|
+
|
|
6
|
+
- if sent_at.present?
|
|
7
|
+
.mt-2.text-right.text-xs.text-gray-400
|
|
8
|
+
= sent_at
|
|
9
|
+
= content
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tramway
|
|
4
|
+
module Chats
|
|
5
|
+
module Messages
|
|
6
|
+
# Renders a message container with alignment and color styles.
|
|
7
|
+
class ContainerComponent < Tramway::BaseComponent
|
|
8
|
+
option :position
|
|
9
|
+
option :text
|
|
10
|
+
option :sent_at
|
|
11
|
+
|
|
12
|
+
def position_classes
|
|
13
|
+
case position.to_sym
|
|
14
|
+
when :left
|
|
15
|
+
%w[items-start]
|
|
16
|
+
when :right
|
|
17
|
+
%w[items-end]
|
|
18
|
+
end.join(' ')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def color_classes
|
|
22
|
+
case position.to_sym
|
|
23
|
+
when :left
|
|
24
|
+
%w[bg-gray-800 rounded-tl-md]
|
|
25
|
+
when :right
|
|
26
|
+
%w[bg-blue-600 rounded-tr-md]
|
|
27
|
+
end.join(' ')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.mt-3.overflow-hidden.rounded-xl.border.border-gray-200.bg-white.shadow-sm.dark:border-gray-700.dark:bg-gray-900
|
|
2
|
+
= tramway_table do
|
|
3
|
+
- data.each_with_index do |row, index|
|
|
4
|
+
- if index.zero?
|
|
5
|
+
= tramway_header headers: row
|
|
6
|
+
- else
|
|
7
|
+
= tramway_row do
|
|
8
|
+
- cell_width = row.count > 1 ? "w-1/#{row.count + 1}" : 'w-full'
|
|
9
|
+
|
|
10
|
+
- row.each do |cell|
|
|
11
|
+
= tramway_cell do
|
|
12
|
+
- if cell.to_s.start_with?('http')
|
|
13
|
+
.underline
|
|
14
|
+
= link_to cell, cell, target: '_blank', class: "#{cell_width} break-all"
|
|
15
|
+
- elsif cell.is_a?(Hash)
|
|
16
|
+
- if cell['status'] == 'pending'
|
|
17
|
+
= tramway_cell do
|
|
18
|
+
= helpers.tramway_button text: t('admin.chats.messages.table.add'), path: cell.dig('button', 'path'), color: :green, method: :post
|
|
19
|
+
- elsif cell['status'] == 'added'
|
|
20
|
+
= tramway_cell do
|
|
21
|
+
= helpers.tramway_button text: t('admin.chats.messages.table.remove'), path: cell.dig('button', 'path'), color: :red, method: :delete
|
|
22
|
+
- else
|
|
23
|
+
= cell.inspect
|
|
24
|
+
- elsif cell.is_a?(String) && cell.size > 50
|
|
25
|
+
.text-sm
|
|
26
|
+
= cell
|
|
27
|
+
- else
|
|
28
|
+
= cell
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
4
|
-
#
|
|
5
|
-
|
|
3
|
+
module Tramway
|
|
4
|
+
# Color logic implementation
|
|
5
|
+
module ColorsMethods
|
|
6
6
|
TYPE_COLOR_MAP = {
|
|
7
7
|
default: :gray,
|
|
8
8
|
life: :gray,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
4
|
-
# Description: A
|
|
5
|
-
class FlashComponent <
|
|
3
|
+
module Tramway
|
|
4
|
+
# Description: A Tramway flash message component for displaying notifications.
|
|
5
|
+
class FlashComponent < Tramway::BaseComponent
|
|
6
6
|
option :text
|
|
7
7
|
option :type, optional: true, default: -> {}
|
|
8
8
|
option :color, optional: true, default: -> {}
|
|
9
9
|
option :options, optional: true, default: -> { {} }
|
|
10
10
|
|
|
11
|
+
include Tramway::ColorsMethods
|
|
12
|
+
|
|
11
13
|
def container_classes
|
|
12
14
|
theme_classes(
|
|
13
15
|
classic: 'fixed top-4 right-4 z-50 space-y-2'
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require 'tramway/utils/field'
|
|
4
4
|
|
|
5
|
-
module
|
|
5
|
+
module Tramway
|
|
6
6
|
module Form
|
|
7
7
|
# Provides Tailwind-styled forms
|
|
8
8
|
# rubocop:disable Metrics/ClassLength
|
|
9
9
|
class Builder < Tramway::Views::FormBuilder
|
|
10
10
|
include Tramway::Utils::Field
|
|
11
|
+
include Tramway::ColorsMethods
|
|
11
12
|
|
|
12
13
|
def initialize(object_name, object, template, options)
|
|
13
14
|
super
|
|
@@ -18,7 +19,7 @@ module Tailwinds
|
|
|
18
19
|
def common_field(component_name, input_method, attribute, **options, &)
|
|
19
20
|
sanitized_options = sanitize_options(options)
|
|
20
21
|
|
|
21
|
-
component_class = "
|
|
22
|
+
component_class = "Tramway::Form::#{component_name.to_s.camelize}Component".constantize
|
|
22
23
|
|
|
23
24
|
render(component_class.new(
|
|
24
25
|
input: input(input_method),
|
|
@@ -47,7 +48,7 @@ module Tailwinds
|
|
|
47
48
|
def password_field(attribute, **options, &)
|
|
48
49
|
sanitized_options = sanitize_options(options)
|
|
49
50
|
|
|
50
|
-
render(
|
|
51
|
+
render(Tramway::Form::TextFieldComponent.new(
|
|
51
52
|
input: input(:password_field),
|
|
52
53
|
**default_options(attribute, sanitized_options)
|
|
53
54
|
), &)
|
|
@@ -65,7 +66,7 @@ module Tailwinds
|
|
|
65
66
|
sanitized_options = sanitize_options(options)
|
|
66
67
|
input = super(attribute, **sanitized_options.merge(class: :hidden))
|
|
67
68
|
|
|
68
|
-
render(
|
|
69
|
+
render(Tramway::Form::FileFieldComponent.new(input:, **default_options(attribute, sanitized_options)), &)
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def check_box(attribute, **, &)
|
|
@@ -75,7 +76,7 @@ module Tailwinds
|
|
|
75
76
|
def select(attribute, collection, **options, &)
|
|
76
77
|
sanitized_options = sanitize_options(options)
|
|
77
78
|
|
|
78
|
-
render(
|
|
79
|
+
render(Tramway::Form::SelectComponent.new(
|
|
79
80
|
input: input(:select),
|
|
80
81
|
value: sanitized_options[:selected] || object.public_send(attribute),
|
|
81
82
|
collection: explicitly_add_blank_option(collection, sanitized_options),
|
|
@@ -86,7 +87,7 @@ module Tailwinds
|
|
|
86
87
|
def multiselect(attribute, collection, **options, &)
|
|
87
88
|
sanitized_options = sanitize_options(options)
|
|
88
89
|
|
|
89
|
-
render(
|
|
90
|
+
render(Tramway::Form::MultiselectComponent.new(
|
|
90
91
|
input: input(:text_field),
|
|
91
92
|
value: sanitized_options[:value] || sanitized_options[:selected] || object.public_send(attribute),
|
|
92
93
|
collection:,
|
|
@@ -98,7 +99,7 @@ module Tailwinds
|
|
|
98
99
|
sanitized_options = sanitize_options(options)
|
|
99
100
|
|
|
100
101
|
render(
|
|
101
|
-
|
|
102
|
+
Tramway::ButtonComponent.new(
|
|
102
103
|
text: action,
|
|
103
104
|
size: form_size,
|
|
104
105
|
type: :will,
|
|
@@ -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
|