tramway 3.1.1.3 → 3.1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdfbb8e7d69f349ee20bc52b7fe51f9a150c09bceb8d7892804d33dd926e36d8
4
- data.tar.gz: e5b62beadc816266f03fbfc49a1a72ec2ffc7883ec59930192b5bc76f2e701c2
3
+ metadata.gz: 3132f9d101d25cd7cb9cacf33921c0d10b70bce3fed987b4d50ade050611d40c
4
+ data.tar.gz: d6a2e0b3b9b5548d3ddb393ea3b29e005571dbfbe64a62d0920d17e206e6323f
5
5
  SHA512:
6
- metadata.gz: b550f718d7f5c954f9606b5cea2deed8144336b4e5d05ab51ab33a16544c90fb78ffea04a378b43002c55687e723789944f196ce9f22b0335aeb436e0520829c
7
- data.tar.gz: 502e0c85c6f0a28ef46d22958147a5ba3b55b6350f8d710b34bd35354a68165be61c938c87e3414caf5436b7b78c4c802b409560a65d3fa52022e2bf64b7fab7
6
+ metadata.gz: 8c630a5d57c0ef7e1c4a1d656975dffd3aa51637439cc16a816198b15d90385b129c7c8b4178fb83e4c1fb1fdaaa46e9de2f77e57835f70994dfc13cf6ee7896
7
+ data.tar.gz: bfc68e7d6f29b8a5060dd2235a3d7d18afe7bcf33330882d8b8104fd3d2dbb65f2d9880e94e66fff003f66706f37d2a659d1cfb6c3b858e6ab7e18f7771f67da
data/README.md CHANGED
@@ -53,7 +53,8 @@ bin/rails g tramway:install
53
53
  ```
54
54
 
55
55
  The install generator adds the required gems (`haml-rails`, `kaminari`, `view_component`, `dry-initializer`, and `dry-monads`) to your
56
- application's Gemfile—if they are not present—and appends the Tailwind safelist configuration Tramway ships with.
56
+ application's Gemfile—if they are not present—appends the Tailwind safelist configuration Tramway ships with, and injects the
57
+ Trix stylesheet and JavaScript tags into your application layout (required for `rich_text_area`).
57
58
 
58
59
  ## Getting Started
59
60
 
@@ -920,6 +921,8 @@ choose when it appears. Supported events are `:hover` and `:onclick`; hover is t
920
921
  `tramway_chat` renders the chat experience bundled with Tramway. Provide a chat ID, a list of message hashes, and the URL
921
922
  that receives new messages. Each message must include an `:id` and a `:type` (either `:sent` or `:received`). Additional
922
923
  message fields like `text`, `data`, or `sent_at` are forwarded to `tramway/chats/message_component`.
924
+ Message text preserves normal word wrapping and only breaks oversized words or links when needed to keep content inside the
925
+ message bubble.
923
926
 
924
927
  Use `send_messages_enabled:` to control whether users can send new messages from the rendered form. It defaults to `true`.
925
928
  When set to `false`, the text field is disabled and the waiting placeholder is shown.
@@ -1203,6 +1206,7 @@ Checkboxes render dark while unchecked and use the light primary checked state.
1203
1206
  <%= f.select :role, [:admin, :user] %>
1204
1207
  <%= f.date_field :birth_date %>
1205
1208
  <%= f.datetime_field :confirmed_at %>
1209
+ <%= f.rich_text_area :bio %>
1206
1210
  <%= f.tramway_select :permissions, [['Create User', 'create_user'], ['Update user', 'update_user']] %>
1207
1211
  <%= f.file_field :file %>
1208
1212
  <%= f.submit 'Create User' %>
@@ -1213,6 +1217,7 @@ will render [this](https://play.tailwindcss.com/xho3LfjKkK)
1213
1217
 
1214
1218
  Use `size:` to control the input sizing (`:small`, `:medium`, or `:large`). The default is `:medium`, and supported inputs
1215
1219
  rendered within the form will use the same size value.
1220
+ Date and datetime fields open the browser's native picker when clicking anywhere in the input, not only the picker icon.
1216
1221
 
1217
1222
  ```erb
1218
1223
  <%= tramway_form_for @user, size: :large do |f| %>
@@ -1238,10 +1243,12 @@ Available form helpers:
1238
1243
  * password_field
1239
1244
  * file_field
1240
1245
  * check_box
1246
+ * checkbox (alias for check_box)
1241
1247
  * select
1242
1248
  * date_field
1243
1249
  * datetime_field
1244
1250
  * time_field
1251
+ * rich_text_area (Action Text/Trix editor with Tramway dark form classes)
1245
1252
  * tramway_select ([Stimulus-based](https://github.com/Purple-Magic/tramway#stimulus-based-inputs))
1246
1253
  * submit
1247
1254
 
@@ -1283,6 +1290,60 @@ same select field.
1283
1290
  <% end %>
1284
1291
  ```
1285
1292
 
1293
+ #### Rich text area
1294
+
1295
+ `rich_text_area` (and its alias `rich_textarea`) renders a [Trix](https://trix-editor.org/) rich text editor
1296
+ with Tramway's dark-theme Tailwind styling. Trix is bundled with Rails and does not require an additional gem.
1297
+
1298
+ Before using `rich_text_area`, the Trix stylesheet and JavaScript must be present in your application layout.
1299
+ The `tramway:install` generator adds them automatically. To add them manually:
1300
+
1301
+ ```haml
1302
+ -# app/views/layouts/application.html.haml
1303
+ = stylesheet_link_tag "trix", "data-turbo-track": "reload"
1304
+ = javascript_include_tag "trix", "data-turbo-track": "reload", defer: true
1305
+ ```
1306
+
1307
+ ```erb
1308
+ <%# app/views/layouts/application.html.erb %>
1309
+ <%= stylesheet_link_tag "trix", "data-turbo-track": "reload" %>
1310
+ <%= javascript_include_tag "trix", "data-turbo-track": "reload", defer: true %>
1311
+ ```
1312
+
1313
+ Basic usage:
1314
+
1315
+ ```erb
1316
+ <%= tramway_form_for @post do |f| %>
1317
+ <%= f.rich_text_area :body %>
1318
+ <% end %>
1319
+ ```
1320
+
1321
+ Custom label and extra CSS class:
1322
+
1323
+ ```erb
1324
+ <%= tramway_form_for @post do |f| %>
1325
+ <%= f.rich_text_area :body, label: 'Post body', class: 'min-h-60' %>
1326
+ <% end %>
1327
+ ```
1328
+
1329
+ Suppress the label:
1330
+
1331
+ ```erb
1332
+ <%= tramway_form_for @post do |f| %>
1333
+ <%= f.rich_text_area :body, label: false %>
1334
+ <% end %>
1335
+ ```
1336
+
1337
+ Pass `data` attributes for Stimulus controllers:
1338
+
1339
+ ```erb
1340
+ <%= tramway_form_for @post do |f| %>
1341
+ <%= f.rich_text_area :body, data: { controller: 'mentions' } %>
1342
+ <% end %>
1343
+ ```
1344
+
1345
+ `size:` is a Tramway-level option and is not forwarded to the `<trix-editor>` element.
1346
+
1286
1347
  #### Stimulus-based inputs
1287
1348
 
1288
1349
  `tramway_form_for` provides Tailwind-styled Stimulus-based custom inputs.
@@ -1339,7 +1400,6 @@ With `remote: true`, Tramway submits the form on each input `change` via inline
1339
1400
  ### Tailwind-styled pagination for Kaminari
1340
1401
 
1341
1402
  Tramway uses [Tailwind](https://tailwindcss.com/) by default. It has tailwind-styled pagination for [kaminari](https://github.com/kaminari/kaminari).
1342
- Pagination components use hardcoded dark shadcn-style classes and do not render a separate light theme.
1343
1403
 
1344
1404
  #### How to use
1345
1405
 
@@ -56,6 +56,14 @@ module Tramway
56
56
  common_field(:text_area, :text_area, attribute, **, &)
57
57
  end
58
58
 
59
+ def rich_text_area(attribute, **, &)
60
+ rich_text_area_field(:rich_text_area, attribute, **, &)
61
+ end
62
+
63
+ def rich_textarea(attribute, **, &)
64
+ rich_text_area_field(:rich_textarea, attribute, **, &)
65
+ end
66
+
59
67
  def password_field(attribute, **options, &)
60
68
  sanitized_options = sanitize_options(options)
61
69
 
@@ -88,6 +96,8 @@ module Tramway
88
96
  common_field(:checkbox, :check_box, attribute, **, &)
89
97
  end
90
98
 
99
+ alias checkbox check_box
100
+
91
101
  def select(attribute, collection, **options, &)
92
102
  if options[:multiple] || options[:autocomplete]
93
103
  tramway_select(attribute, collection, **options, &)
@@ -138,6 +148,15 @@ module Tramway
138
148
  ), &)
139
149
  end
140
150
 
151
+ def rich_text_area_field(input_method, attribute, **options, &)
152
+ sanitized_options = sanitize_options(options)
153
+
154
+ render(Tramway::Form::RichTextAreaComponent.new(
155
+ input: input(input_method),
156
+ **default_options(attribute, sanitized_options)
157
+ ), &)
158
+ end
159
+
141
160
  def input(method_name)
142
161
  unbound_method = self.class.superclass.instance_method(method_name)
143
162
  unbound_method.bind(self)
@@ -3,4 +3,4 @@
3
3
  = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
- = @input.call @attribute, **@options.merge(class: classes), value: @value
6
+ = @input.call @attribute, **picker_options(classes), value: @value
@@ -4,6 +4,13 @@ module Tramway
4
4
  module Form
5
5
  # Tailwind-styled date field
6
6
  class DateFieldComponent < TailwindComponent
7
+ PICKER_ONCLICK = 'try { this.showPicker && this.showPicker() } catch (error) {}'
8
+
9
+ private
10
+
11
+ def picker_options(classes)
12
+ @options.merge(class: classes, onclick: [@options[:onclick], PICKER_ONCLICK].compact.join('; '))
13
+ end
7
14
  end
8
15
  end
9
16
  end
@@ -3,4 +3,4 @@
3
3
  = component('tramway/form/label', for: @for, class: 'mb-0') do
4
4
  = @label
5
5
  - classes = "#{size_class(:text_input)} #{text_input_base_classes}"
6
- = @input.call @attribute, **@options.merge(class: classes), value: @value
6
+ = @input.call @attribute, **picker_options(classes), value: @value
@@ -4,6 +4,13 @@ module Tramway
4
4
  module Form
5
5
  # Tailwind-styled datetime field
6
6
  class DatetimeFieldComponent < TailwindComponent
7
+ PICKER_ONCLICK = 'try { this.showPicker && this.showPicker() } catch (error) {}'
8
+
9
+ private
10
+
11
+ def picker_options(classes)
12
+ @options.merge(class: classes, onclick: [@options[:onclick], PICKER_ONCLICK].compact.join('; '))
13
+ end
7
14
  end
8
15
  end
9
16
  end
@@ -0,0 +1,14 @@
1
+ %div{ class: default_container_classes }
2
+ :css
3
+ trix-toolbar .trix-button-group {
4
+ border-color: #52525b;
5
+ }
6
+
7
+ trix-toolbar .trix-button:not(.trix-active)::before {
8
+ filter: invert(1);
9
+ }
10
+
11
+ - if @label
12
+ = component('tramway/form/label', for: @for, class: 'mb-0') do
13
+ = @label
14
+ = @input.call @attribute, **rich_text_area_options
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Form
5
+ # Tailwind-styled rich text area
6
+ class RichTextAreaComponent < TailwindComponent
7
+ RICH_TEXT_AREA_CLASSES = [
8
+ 'trix-content',
9
+ 'prose',
10
+ 'prose-invert',
11
+ 'max-w-none',
12
+ 'w-full',
13
+ 'min-h-40',
14
+ 'rounded-md',
15
+ 'border',
16
+ 'border-zinc-800',
17
+ 'bg-zinc-950',
18
+ '!bg-zinc-950',
19
+ 'text-zinc-50',
20
+ '!text-zinc-50',
21
+ 'shadow-sm',
22
+ 'transition-colors',
23
+ 'focus-visible:outline-none',
24
+ 'focus-visible:ring-2',
25
+ 'focus-visible:ring-zinc-300',
26
+ 'focus-visible:ring-offset-2',
27
+ 'focus-visible:ring-offset-zinc-950',
28
+ 'disabled:cursor-not-allowed',
29
+ 'disabled:opacity-50'
30
+ ].freeze
31
+
32
+ private
33
+
34
+ def rich_text_area_options
35
+ options.dup.tap do |rich_text_options|
36
+ custom_class = rich_text_options.delete(:class) || rich_text_options.delete('class')
37
+ rich_text_options[:class] = [RICH_TEXT_AREA_CLASSES, custom_class].compact.join(' ')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,2 +1,2 @@
1
- %div.max-w-full.min-w-0.break-all
1
+ %div.max-w-full.min-w-0.break-words
2
2
  = rendered_html
@@ -8,7 +8,7 @@
8
8
  - cells = visible_cells_from(content)
9
9
 
10
10
  - if href.present?
11
- = tag.a href:, class: [desktop_row_classes(cells.count), link_row_classes].join(' '), **default_attributes do
11
+ = tag.a href:, class: [desktop_row_classes(cells.count), link_row_classes, options[:class]].join(' '), **default_attributes, **options.except(:class) do
12
12
  = content
13
13
  - else
14
14
  - if preview
@@ -18,5 +18,5 @@
18
18
 
19
19
  = cell.to_s.html_safe
20
20
 
21
- = tag.div class: desktop_row_classes(cells.count), data: { action: "click->table-row-preview#toggle", controller: "table-row-preview" }, **default_attributes do
21
+ = tag.div class: [desktop_row_classes(cells.count), options[:class]].join(' '), data: { action: "click->table-row-preview#toggle", controller: "table-row-preview" }, **default_attributes, **options.except(:class) do
22
22
  = content
@@ -9,15 +9,13 @@
9
9
 
10
10
  = yield :head
11
11
 
12
- / Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!)
13
- / = tag.link rel: "manifest", href: pwa_manifest_path(format: :json)
14
-
15
12
  %link{rel: "icon", href: "/icon.png", type: "image/png"}
16
13
  %link{rel: "icon", href: "/icon.svg", type: "image/svg+xml"}
17
14
  %link{rel: "apple-touch-icon", href: "/icon.png"}
18
15
 
19
- / Includes all stylesheet files in app/assets/stylesheets
20
16
  = stylesheet_link_tag "tailwind", "data-turbo-track": "reload"
17
+ = stylesheet_link_tag "trix", "data-turbo-track": "reload"
18
+ = javascript_include_tag "trix", "data-turbo-track": "reload", defer: true
21
19
 
22
20
  %body.bg-zinc-950.text-zinc-50
23
21
  = tramway_navbar title: 'Tramway'
@@ -27,6 +27,7 @@ module.exports = {
27
27
  'pt-16',
28
28
  'min-h-8',
29
29
  'bg-zinc-950',
30
+ '!bg-zinc-950',
30
31
  'bg-zinc-950/95',
31
32
  'bg-zinc-950/80',
32
33
  'bg-zinc-900',
@@ -34,6 +35,7 @@ module.exports = {
34
35
  'bg-zinc-900/80',
35
36
  'border-zinc-800',
36
37
  'text-zinc-50',
38
+ '!text-zinc-50',
37
39
  'text-zinc-100',
38
40
  'text-zinc-200',
39
41
  'text-zinc-400',
@@ -146,7 +148,7 @@ module.exports = {
146
148
  'text-blue-100/90',
147
149
  'text-red-200',
148
150
  'underline',
149
- 'break-all',
151
+ 'break-words',
150
152
  'animate-spin',
151
153
  'placeholder:text-gray-400',
152
154
  'focus:outline-none',
@@ -221,6 +223,9 @@ module.exports = {
221
223
  'hidden',
222
224
  'text-xl',
223
225
  'font-bold',
226
+ 'prose',
227
+ 'prose-invert',
228
+ 'max-w-none',
224
229
 
225
230
  // === Button base shell ===
226
231
  'inline-flex',
@@ -338,6 +343,7 @@ module.exports = {
338
343
  'min-h-10',
339
344
  'min-h-12',
340
345
  'min-h-15',
346
+ 'min-h-40',
341
347
  'max-w-full',
342
348
 
343
349
  // === Spacing utilities ===
@@ -380,7 +386,6 @@ module.exports = {
380
386
  'left-1/2',
381
387
  '-translate-x-1/2',
382
388
  'z-50',
383
- 'mb-2',
384
389
  'w-max',
385
390
  'min-w-40',
386
391
  'max-w-sm',
@@ -95,6 +95,28 @@ module Tramway
95
95
  @agents_file_path ||= File.join(destination_root, 'AGENTS.md')
96
96
  end
97
97
 
98
+ def application_layout_haml_path
99
+ @application_layout_haml_path ||= File.join(destination_root, 'app/views/layouts/application.html.haml')
100
+ end
101
+
102
+ def application_layout_erb_path
103
+ @application_layout_erb_path ||= File.join(destination_root, 'app/views/layouts/application.html.erb')
104
+ end
105
+
106
+ def trix_haml_tags
107
+ " = stylesheet_link_tag \"trix\", \"data-turbo-track\": \"reload\"\n " \
108
+ "= javascript_include_tag \"trix\", \"data-turbo-track\": \"reload\", defer: true\n"
109
+ end
110
+
111
+ def trix_erb_tags
112
+ " <%= stylesheet_link_tag \"trix\", \"data-turbo-track\": \"reload\" %>\n " \
113
+ "<%= javascript_include_tag \"trix\", \"data-turbo-track\": \"reload\", defer: true %>\n"
114
+ end
115
+
116
+ def trix_already_present?(content)
117
+ content.include?('stylesheet_link_tag "trix"') || content.include?("stylesheet_link_tag 'trix'")
118
+ end
119
+
98
120
  def codex_agents_instruction
99
121
  'If you are Codex, use the tramway-skill skill for Tramway/Rails work in this project. ' \
100
122
  'If tramway-skill is not installed, install it from https://github.com/Purple-Magic/tramway-skill/.'
@@ -323,6 +345,34 @@ module Tramway
323
345
 
324
346
  File.write(controllers_index_path, updated)
325
347
  end
348
+
349
+ def ensure_trix_in_application_layout
350
+ if File.exist?(application_layout_haml_path)
351
+ ensure_trix_in_haml_layout
352
+ elsif File.exist?(application_layout_erb_path)
353
+ ensure_trix_in_erb_layout
354
+ end
355
+ end
356
+
357
+ private
358
+
359
+ def ensure_trix_in_haml_layout
360
+ content = File.read(application_layout_haml_path)
361
+ return if trix_already_present?(content)
362
+ return unless content.match?(/^\s+%body/)
363
+
364
+ updated = content.sub(/^(\s+%body)/, "#{trix_haml_tags}\\1")
365
+ File.write(application_layout_haml_path, updated)
366
+ end
367
+
368
+ def ensure_trix_in_erb_layout
369
+ content = File.read(application_layout_erb_path)
370
+ return if trix_already_present?(content)
371
+ return unless content.include?('</head>')
372
+
373
+ updated = content.sub('</head>', "#{trix_erb_tags} </head>")
374
+ File.write(application_layout_erb_path, updated)
375
+ end
326
376
  end
327
377
  end
328
378
  end
@@ -23,7 +23,7 @@ module Tramway
23
23
 
24
24
  def field_name(field_data)
25
25
  case field_data.to_sym
26
- when :text_area, :select, :tramway_select, :check_box
26
+ when :text_area, :rich_text_area, :rich_textarea, :select, :tramway_select, :check_box
27
27
  field_data
28
28
  when :checkbox
29
29
  :check_box
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '3.1.1.3'
4
+ VERSION = '3.1.2.1'
5
5
  end
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: 3.1.1.3
4
+ version: 3.1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - kalashnikovisme
@@ -183,6 +183,8 @@ files:
183
183
  - app/components/tramway/form/label_component.rb
184
184
  - app/components/tramway/form/number_field_component.html.haml
185
185
  - app/components/tramway/form/number_field_component.rb
186
+ - app/components/tramway/form/rich_text_area_component.html.haml
187
+ - app/components/tramway/form/rich_text_area_component.rb
186
188
  - app/components/tramway/form/select_component.html.haml
187
189
  - app/components/tramway/form/select_component.rb
188
190
  - app/components/tramway/form/text_area_component.html.haml