tramway 2.2.2.7 → 2.2.3

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: d0aaa3638083ec75bcbbcd0004e0bce38e3d8ec1bcd49a0aff0ced804ad96889
4
- data.tar.gz: 1abdc26dfa0d58657f6df30405f919f24e7b7bde9fcb81a769fd2f3689100b33
3
+ metadata.gz: 073b1293c7f3778243b4402bd9db528eea16b9897145f5ac7a50bb0751607dfd
4
+ data.tar.gz: 980e71accc0572795aaa87764af580f50d883fa504b4b148fd5224826941c110
5
5
  SHA512:
6
- metadata.gz: 6f8827bba6e448bd4af639c2a4490ba3cc954082874c60846d7e479d211000afa4bd47b2b9ab2608d2e61117bf12117e618f2d7e8d75715dddd9030a813a1a12
7
- data.tar.gz: 767e7bbb7a246905feb4d90f24dc5d783fc05cb8496fabafc70da4dd423a715ec102b494e1e185202128693cdaad6a8c2ac0219d5c563947704b0bc5eb9322e5
6
+ metadata.gz: 4819641881ea15d5ef0211470aab28fad0c758ac2f733bab1c8b2a319ae0b4794973df321a45d0bc054c273c9d567db1c86325a9b2739e40a818b101106c9b3a
7
+ data.tar.gz: 331ddb0387c6ee4339e1988a69c79cb9851d5f33f2ccd519036dec9d88c5b8d7fcd3744418e881550c915f568a6efb8fe352c4e80d0a08eb79b3833469221662
data/README.md CHANGED
@@ -264,7 +264,7 @@ Tramway.configure do |config|
264
264
  end
265
265
  ```
266
266
 
267
- **form_fields method**
267
+ **fields method**
268
268
 
269
269
  Use `form_fields` in your form class to customize which form helpers get rendered and which options are passed to them.
270
270
  Each field must map to a form helper method name. When you need to pass options, use a hash where `:type` is the helper
@@ -275,6 +275,7 @@ class UserForm < Tramway::BaseForm
275
275
  properties :email, :about_me
276
276
 
277
277
  fields email: :email,
278
+ name: :text,
278
279
  about_me: {
279
280
  type: :text_area,
280
281
  rows: 5
@@ -287,6 +288,7 @@ The configuration above renders:
287
288
  ```erb
288
289
  <%= tramway_form_for .... do |f| %>
289
290
  <%= f.email_field :email %>
291
+ <%= f.text_field :name %>
290
292
  <%= f.text_area :about_me, rows: 5 %>
291
293
  ```
292
294
 
@@ -893,13 +895,46 @@ component applies.
893
895
 
894
896
  Tramway ships with helpers for common UI patterns built on top of Tailwind components.
895
897
 
896
- * `tramway_button` renders a button-styled form submit by default and accepts `path`, optional `text`, HTTP `method`, and styling
897
- options such as `color`, `type`, and `size`. It uses Rails' `button_to` helper by default (or when `link: false` is passed),
898
- and switches to `link_to` when you set `link: true`.
898
+ `tramway_button` helper is designed for developers who don't want to think about what type of button must be used now. It can render 3 types of buttons: `a` (links), `button`, `form` with button.
899
899
 
900
- ```erb
901
- <%= tramway_button path: user_path(user), text: 'Open profile', link: true %>
902
- ```
900
+ You can set a type to render explicitly:
901
+
902
+ ```ruby
903
+ tramway_button tag: :a, text: 'Link' #=> <a class=BUTTON_CLASSES>Link</a>
904
+ tramway_button tag: :button, text: 'Link' #=> <button class=BUTTON_CLASSES>Link</button>
905
+ tramway_button tag: :form, text: 'Link' #=> <form><button class=BUTTON_CLASSES>Link</button></form>
906
+ ```
907
+
908
+ OR `tramway_button` choose the most appropriate button type by the arguments received.
909
+
910
+ #### `tramway_button` options
911
+
912
+ **path**
913
+
914
+ Example 1: rendering link
915
+ ```erb
916
+ <%= tramway_button path: '/projects', text: 'Projects' %>
917
+
918
+ #=> <a href='/projects' class="...">Projects</a>
919
+ ```
920
+
921
+ Example 2: rendering form
922
+ ```erb
923
+ <%= tramway_button path: '/projects/1', text: 'Destroy', method: :delete %>
924
+
925
+ #> <form action="/projects/1" method="post">
926
+ #> ....
927
+ #> <button class: "..." type="submit">
928
+ #> Delete
929
+ #> </button>
930
+ ```
931
+
932
+ Example 3: rendering button
933
+ ```
934
+ <%= tramway_button path: '/projects/1', text: 'Edit', tag: :button %>
935
+
936
+ #> <button onclick="window.location.href='/projects/1' class="...">Edit</button>
937
+ ```
903
938
 
904
939
  All additional keyword arguments are forwarded to the underlying component as HTML attributes.
905
940
 
@@ -914,30 +949,6 @@ Tramway ships with helpers for common UI patterns built on top of Tailwind compo
914
949
 
915
950
  The `type` option maps semantic intent to [Lantern Color Palette](https://github.com/TrinityMonsters/tramway/blob/main/README.md#lantern-color-palette).
916
951
 
917
- If none of the predefined semantic types fit your needs, you can supply a Tailwind color family directly using the `color`
918
- option—for example: `color: :gray`. When you pass a custom color ensure the corresponding utility classes exist in your
919
- Tailwind configuration. Add the following safelist entries (adjusting the color name as needed) to `config/tailwind.config.js`:
920
-
921
- ```js
922
- // config/tailwind.config.js
923
- module.exports = {
924
- // ...
925
- safelist: [
926
- // existing entries …
927
- {
928
- pattern: /(bg|hover:bg|dark:bg|dark:hover:bg)-gray-(500|600|700|800)/,
929
- },
930
- ],
931
- }
932
- ```
933
-
934
- Tailwind will then emit the `bg-gray-500`, `hover:bg-gray-700`, `dark:bg-gray-600`, and `dark:hover:bg-gray-800` classes that
935
- Tramway buttons expect when you opt into a custom color.
936
-
937
- ```erb
938
- <%= tramway_button path: user_path(user), text: 'View profile', color: :emerald, data: { turbo: false } %>
939
- ```
940
-
941
952
  * `tramway_badge` renders a Tailwind-styled badge with the provided `text`. Pass a semantic `type` (for example, `:success` or
942
953
  `:danger`) to use the built-in color mappings, or supply a custom Tailwind color family with `color:`. When you opt into a
943
954
  custom color, ensure the corresponding background utilities are available in your Tailwind safelist.
@@ -1,20 +1,20 @@
1
1
  - if text.present?
2
- - if render_a_tag?
2
+ - case @tag
3
+ - when :a
3
4
  = link_to text, path, class: classes, **render_options
4
- - else
5
- - if type&.to_sym == :submit
6
- %button{ type: :submit, name: :commit, class: classes, **render_options }
7
- = text
8
- - else
9
- = button_to text, path, method:, form: form_options, class: classes, **render_options
5
+ - when :button
6
+ %button{ class: classes, **render_options }
7
+ = text
8
+ - when :form
9
+ = helpers.button_to text, path, method:, form: form_options, class: classes, **render_options
10
10
  - else
11
- - if render_a_tag?
11
+ - case @tag
12
+ - when :a
12
13
  = link_to path, class: classes, **render_options do
13
14
  = content
14
- - else
15
- - if type&.to_sym == :submit
16
- %button{ type: :submit, name: :commit, class: classes, **render_options }
17
- = content
18
- - else
19
- = button_to path, method:, class: classes, form: form_options, **render_options do
20
- = content
15
+ - when :button
16
+ %button{ type: :submit, name: :commit, class: classes, **render_options }
17
+ = content
18
+ - when :form
19
+ = helpers.button_to path, method:, class: classes, form: form_options, **render_options do
20
+ = content
@@ -10,10 +10,21 @@ module Tailwinds
10
10
  option :type, optional: true
11
11
  option :size, default: -> { :medium }
12
12
  option :method, optional: true, default: -> { :get }
13
- option :link, optional: true, default: -> { false }
13
+ option :tag, optional: true, default: -> { false }
14
14
  option :options, optional: true, default: -> { {} }
15
15
  option :form_options, optional: true, default: -> { {} }
16
16
 
17
+ def before_render
18
+ return if tag.present?
19
+ return @tag = :button if type == :submit
20
+
21
+ URI.parse(path)
22
+
23
+ return @tag = :a if method.to_s.downcase == 'get'
24
+
25
+ @tag = :form
26
+ end
27
+
17
28
  def size_classes
18
29
  unless size.in?(%i[small medium large])
19
30
  raise ArgumentError, "Invalid size: #{size}. Valid sizes are :small, :medium, :large."
@@ -29,7 +40,7 @@ module Tailwinds
29
40
  def classes
30
41
  (default_classes +
31
42
  color_classes +
32
- (render_a_tag? ? %w[px-1 h-fit w-fit] : [cursor_class])).compact.join(' ')
43
+ (@tag == :a ? %w[px-1 h-fit w-fit] : [cursor_class])).compact.join(' ')
33
44
  end
34
45
 
35
46
  def default_classes
@@ -57,17 +68,6 @@ module Tailwinds
57
68
  options[:disabled] || false
58
69
  end
59
70
 
60
- def render_a_tag?
61
- return true if link
62
- return true if path == '#' && type != :submit
63
-
64
- uri = URI.parse(path)
65
-
66
- return true if method.to_s.downcase == 'get' && uri.query && !uri.query.empty?
67
-
68
- false
69
- end
70
-
71
71
  def render_options
72
72
  base_options = options.except(:class)
73
73
  return base_options unless stop_cell_propagation?
@@ -88,7 +88,7 @@ module Tailwinds
88
88
  end
89
89
 
90
90
  def cursor_class
91
- if !render_a_tag? && !disabled?
91
+ if @tag != :a && !disabled?
92
92
  'cursor-pointer'
93
93
  else
94
94
  'cursor-not-allowed'
@@ -92,7 +92,7 @@ module Tailwinds
92
92
  def submit(action, **options, &)
93
93
  sanitized_options = sanitize_options(options)
94
94
 
95
- render(Tailwinds::Form::SubmitButtonComponent.new(action, size: form_size, **sanitized_options), &)
95
+ render(Tailwinds::ButtonComponent.new(text: action, size: form_size, type: :submit, **sanitized_options), &)
96
96
  end
97
97
 
98
98
  private
@@ -9,7 +9,8 @@
9
9
  = tramway_button text: t('tramway.actions.edit'),
10
10
  path: Tramway::Engine.routes.url_helpers.public_send(entity.routes.edit, item.id),
11
11
  type: :will,
12
- size: :small
12
+ size: :small,
13
+ tag: :form
13
14
 
14
15
  - if @entity.page(:destroy).present?
15
16
  = tramway_button text: t('tramway.actions.destroy'),
data/docs/AGENTS.md CHANGED
@@ -78,14 +78,285 @@ config/
78
78
 
79
79
  ## Rules
80
80
 
81
- - Use Tramway Entities and Tramway actions (index, show, create, update, destroy) by default unless custom behavior is needed. Configure in `config/initializers/tramway.rb`.
82
- - Normalize input with `normalizes` (from Tramway) for attributes like email, phone, etc.
83
- - Use Tramway Navbar for navigation
84
- - Use Tramway Flash for user notifications.
85
- - Use Tramway Table for tabular data display.
86
- - Use Tramway Button for buttons.
87
- - Use `tramway_form_for` instead `form_with`, `form_for`
88
- - Inherit all components from Tramway::BaseComponent
81
+ ### Rule 1
82
+ If CRUD is requested or some default actions like (index, show, create, update, destroy) are requestsed, use Tramway Entities by default unless custom behavior is needed. Configure in `config/initializers/tramway.rb`. Do not create controllers, views, or routes manually for CRUD actions if Tramway Entities can handle it.
83
+
84
+ When `namespace` is mentioned in the request, configure it in the entity definition.
85
+
86
+ Example of CRUD configuration for model `Participant`:
87
+
88
+ *config/initializers/tramway.rb*:
89
+ ```ruby
90
+ Tramway.configure do |config|
91
+ config.entities = [
92
+ {
93
+ name: :participant,
94
+ pages: [
95
+ { action: :index },
96
+ { action: :show },
97
+ { action: :create },
98
+ { action: :update },
99
+ { action: :destroy }
100
+ ]
101
+ }
102
+ ]
103
+ end
104
+ ```
105
+
106
+ ### Rule 2
107
+ Normalize input with `normalizes` (from Tramway) for attributes like email, phone, etc. Don't use `normalizes` in model unless it requested explicitly.
108
+
109
+ ### Rule 3
110
+ Use Tramway Navbar for navigation
111
+
112
+ ### Rule 4
113
+ Use Tramway Flash for user notifications.
114
+
115
+ ### Rule 5
116
+ Use Tramway Table for tabular data display.
117
+
118
+ ### Rule 6
119
+ Use Tramway Button for buttons.
120
+
121
+ ### Rule 7
122
+ Use `tramway_form_for` instead `form_with`, `form_for`
123
+
124
+ ### Rule 8
125
+ Inherit all components from Tramway::BaseComponent
126
+
127
+ ### Rule 9
128
+ If page `create` or `update` is configured for an entity, use Tramway Form pattern for forms. Visible fields are configured via `form_fields` method.
129
+
130
+ Use form_fields in your form class to customize which form helpers get rendered and which options are passed to them. Each field must map to a form helper method name. When you need to pass options, use a hash where :type is the helper method name and the remaining keys are passed as named arguments.
131
+
132
+ Example:
133
+
134
+ *app/forms/user_form.rb*:
135
+ ```ruby
136
+ class UserForm < Tramway::BaseForm
137
+ properties :email, :about_me
138
+
139
+ fields email: :email,
140
+ name: :text,
141
+ about_me: {
142
+ type: :text_area,
143
+ rows: 5
144
+ }
145
+ end
146
+ ```
147
+
148
+ ### Rule 10
149
+ Do not use `strong_parameters` in controllers. Use Tramway Form pattern for parameter whitelisting.
150
+
151
+ ### Rule 11
152
+ Create tests for show models pages inside `spec/features/#{pluralized model_name}/show_spec.rb` using RSpec and Capybara if it needed.
153
+
154
+ Here is an example for `Task` model:
155
+
156
+ ```ruby
157
+ describe 'Tasks Show Page', type: :feature do
158
+ let!(:task) do
159
+ create(:task)
160
+ end
161
+
162
+ it 'displays the task' do
163
+ visit task_path(task)
164
+
165
+ expect(page).to have_current_path(task_path(task))
166
+ expect(page).to have_content(task.name)
167
+ expect(page).to have_content('Pending')
168
+ end
169
+ end
170
+ ```
171
+
172
+ ### Rule 12
173
+ Create tests for index models pages inside `spec/features/#{pluralized model_name}/index_spec.rb` using RSpec and Capybara if it needed.
174
+
175
+ Here is an example for `Project` model:
176
+
177
+ ```ruby
178
+ describe 'Projects Index Page', type: :feature do
179
+ let!(:projects) { create_list(:project, 3) }
180
+
181
+ describe 'visiting the index page' do
182
+ before do
183
+ visit projects_path
184
+ end
185
+
186
+ it 'displays all projects' do
187
+ expect(page).to have_current_path(projects_path)
188
+
189
+ projects.each do |project|
190
+ expect(page).to have_content(project.name)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ ```
196
+
197
+ ### Rule 13
198
+ Create tests for create models pages inside `spec/features/#{pluralized model_name}/create_spec.rb` using RSpec and Capybara if it needed.
199
+
200
+ Here is an example for `Project` model:
201
+
202
+ ```ruby
203
+ describe 'Projects Create', type: :feature do
204
+ let!(:project_attributes) do
205
+ attributes_for(:project)
206
+ end
207
+
208
+ let(:project) { Project.last }
209
+
210
+ it 'creates a new project' do
211
+ visit new_project_path
212
+
213
+ expect(page).to have_current_path(new_project_path)
214
+
215
+ fill_in 'project[name]', with: project_attributes[:name]
216
+ fill_in 'project[description]', with: project_attributes[:description]
217
+ select project_attributes[:project_type], from: 'project[project_type]'
218
+
219
+ click_on 'Save'
220
+
221
+ expect(project).to have_attributes(
222
+ name: project_attributes[:name],
223
+ description: project_attributes[:description],
224
+ )
225
+ end
226
+ end
227
+ ```
228
+
229
+ ### Rule 14
230
+ Create tests for update models pages inside `spec/features/#{pluralized model_name}/update_spec.rb` using RSpec and Capybara if it needed.
231
+
232
+ Here is an example for `Project` model:
233
+
234
+ ```ruby
235
+ describe 'Projects Update', type: :feature do
236
+ let!(:project) do
237
+ create('project', user: current_user)
238
+ end
239
+
240
+ let!(:project_attributes) do
241
+ attributes_for(:project)
242
+ end
243
+
244
+ it 'updates the project' do
245
+ visit edit_project_path(project)
246
+
247
+ expect(page).to have_current_path(edit_project_path(project))
248
+
249
+ fill_in 'project[name]', with: project_attributes[:name]
250
+ fill_in 'project[description]', with: project_attributes[:description]
251
+
252
+ click_on 'Save'
253
+
254
+ expect(page).to have_content('Project was successfully updated.')
255
+ expect(page).to have_content(project_attributes[:name])
256
+ expect(page).to have_content(project_attributes[:description])
257
+
258
+ project.reload
259
+
260
+ expect(project).to have_attributes(
261
+ name: project_attributes[:name],
262
+ description: project_attributes[:description]
263
+ )
264
+ end
265
+ end
266
+ ```
267
+
268
+ ### Rule 15
269
+ Create tests for destroy models pages inside `spec/features/#{pluralized model_name}/destroy_spec.rb` using RSpec and Capybara if it needed.
270
+
271
+ Here is an example for `Project` model:
272
+
273
+ ```ruby
274
+ describe 'Project Destroy', type: :feature do
275
+ let!(:project) do
276
+ create('project')
277
+ end
278
+
279
+ it 'destroys the project' do
280
+ visit project_path(project)
281
+
282
+ expect(page).to have_current_path(project_path(project))
283
+
284
+ click_on 'Delete'
285
+
286
+ expect(page).to have_content('Project was successfully destroyed.')
287
+
288
+ expect { Project.find(project.id) }.to raise_error(ActiveRecord::RecordNotFound)
289
+ end
290
+ end
291
+ ```
292
+
293
+ ### Rule 16
294
+ If you created any tests for Tramway Entities pages, make sure to add this to `spec/rails_helper.rb`.
295
+
296
+ *spec/rails_helper.rb*:
297
+ ```ruby
298
+ RSpec.configure do |config|
299
+ config.include Tramway::Helpers::RoutesHelper, type: :feature
300
+ end
301
+ ```
302
+
303
+ ### Rule 17
304
+ If application has authentication in the web then use `application_controller` config in `config/initializers/tramway.rb` to setup authentication method for Tramway Entities.
305
+
306
+ *config/initializers/tramway.rb*:
307
+ ```ruby
308
+ Tramway.configure do |config|
309
+ config.application_controller = 'ApplicationController'
310
+ end
311
+ ```
312
+
313
+ ### Rule 18
314
+ If you use `index` page for Tramway Entity, make sure to create `index_attributes` method in the entity decorator.
315
+
316
+ Example for `Participant` model:
317
+
318
+ *app/decorators/participant_decorator.rb*:
319
+ ```ruby
320
+ class ParticipantDecorator < Tramway::BaseDecorator
321
+ def self.index_attributes
322
+ [
323
+ :id,
324
+ :name,
325
+ :email,
326
+ :created_at
327
+ ]
328
+ end
329
+ end
330
+ ```
331
+
332
+ ### Rule 19
333
+ In specs ALWAYS use factories (FactoryBot gem) to create models and attributes hash. In case there is no factory for the model, create one inside `spec/factories/#{pluralized model_name}.rb`.
334
+
335
+ ### Rule 20
336
+ In case you need enumerize for model attribute, make sure to use `enumerize` gem for that. DO NOT use `boolean` or `integer` types for enumerations.
337
+
338
+ ### Rule 21
339
+ In case you need something that looks like enumerize but it's a process state, use `aasm` gem for that.
340
+
341
+ ### Rule 22
342
+ Use model scopes instead of creating private methods for object collections.
343
+
344
+ ### Rule 23
345
+ Use `tramway_title` for the main title on pages
346
+
347
+ Example
348
+ *app/models/user.rb*
349
+ ```ruby
350
+ class User < ApplicationRecord
351
+ scope :this_month_registered_users, -> { where(created_at: Time.current.all_month) }
352
+ end
353
+ ```
354
+
355
+ ### Rule 24
356
+ In case you implementing API, use `api` namespaces for forms and decorators.
357
+
358
+ ### Rule 25
359
+ DO NOT use `#{model_name}_params` method with `permit` method inside controllers. When you use `tramway_form`, it's unnecessary.
89
360
 
90
361
  ## Controller Patterns
91
362
 
@@ -141,6 +412,7 @@ class ParticipantsController < ApplicationController
141
412
  end
142
413
  ```
143
414
 
415
+
144
416
  ---
145
417
 
146
418
  ## Tailwind Practices
@@ -40,12 +40,11 @@ module Tramway
40
40
  component 'tailwinds/table/cell', &
41
41
  end
42
42
 
43
- # rubocop:disable Metrics/ParameterLists
44
- def tramway_button(path: nil, text: nil, method: :get, link: false, form_options: {}, **options, &)
45
- component 'tailwinds/button', text:, path:, method:, link:, form_options:, color: options.delete(:color),
46
- type: options.delete(:type), size: options.delete(:size), options:, &
43
+ def tramway_button(path: nil, text: nil, method: :get, form_options: {}, **options, &)
44
+ component 'tailwinds/button', text:, path:, method:, form_options:, color: options.delete(:color),
45
+ type: options.delete(:type), size: options.delete(:size),
46
+ tag: options.delete(:tag), options:, &
47
47
  end
48
- # rubocop:enable Metrics/ParameterLists
49
48
 
50
49
  def tramway_back_button
51
50
  component 'tailwinds/back_button'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tramway
4
- VERSION = '2.2.2.7'
4
+ VERSION = '2.2.3'
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: 2.2.2.7
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - kalashnikovisme
@@ -178,8 +178,6 @@ files:
178
178
  - app/components/tailwinds/form/number_field_component.rb
179
179
  - app/components/tailwinds/form/select_component.html.haml
180
180
  - app/components/tailwinds/form/select_component.rb
181
- - app/components/tailwinds/form/submit_button_component.html.haml
182
- - app/components/tailwinds/form/submit_button_component.rb
183
181
  - app/components/tailwinds/form/text_area_component.html.haml
184
182
  - app/components/tailwinds/form/text_area_component.rb
185
183
  - app/components/tailwinds/form/text_field_component.html.haml
@@ -1,11 +0,0 @@
1
- .flex.items-center.justify-between
2
- - classes = "#{size_class(:submit_button)} #{submit_button_base_classes}"
3
-
4
- = helpers.tramway_button text: @text,
5
- type: :submit,
6
- name: :commit,
7
- size:,
8
- class: classes,
9
- **@options
10
-
11
- = @content
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Tailwinds
4
- module Form
5
- # Tailwind-styled submit button
6
- class SubmitButtonComponent < TailwindComponent
7
- def initialize(action, size: :medium, **options)
8
- unless size.in?(%i[small medium large])
9
- raise ArgumentError, "Invalid size: #{size}. Valid sizes are :small, :medium, :large."
10
- end
11
-
12
- @text = action.is_a?(String) ? action : action.to_s.capitalize
13
-
14
- super(input: nil, attribute: nil, value: nil, options: options.except(:type), label: nil, for: nil, size:)
15
- end
16
- end
17
- end
18
- end