tramway 3.1.2.8 → 4.0.1.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +134 -18
  3. data/app/assets/builds/tailwind.css +2 -0
  4. data/app/assets/javascripts/tramway/tramway.js +202 -1
  5. data/app/assets/stylesheets/font-awesome.css +2337 -0
  6. data/app/assets/tailwind/application.css +29 -0
  7. data/app/components/tramway/chat_component.html.haml +1 -1
  8. data/app/components/tramway/containers/main_component.rb +1 -3
  9. data/app/components/tramway/containers/narrow_component.rb +5 -4
  10. data/app/components/tramway/form/builder.rb +1 -2
  11. data/app/components/tramway/grid/card_component.html.haml +2 -0
  12. data/app/components/tramway/grid/card_component.rb +37 -0
  13. data/app/components/tramway/grid_component.html.haml +18 -0
  14. data/app/components/tramway/grid_component.rb +76 -0
  15. data/app/components/tramway/native_text_component.rb +1 -2
  16. data/app/components/tramway/nav/item_component.rb +4 -2
  17. data/app/components/tramway/navbar_component.html.haml +131 -102
  18. data/app/components/tramway/navbar_component.rb +84 -2
  19. data/app/components/tramway/table/cell_component.rb +9 -1
  20. data/app/components/tramway/table/header_component.rb +23 -2
  21. data/app/components/tramway/table/row_component.rb +27 -10
  22. data/app/components/tramway/table_component.rb +28 -1
  23. data/app/controllers/tramway/entities_controller.rb +13 -6
  24. data/app/views/tramway/entities/_list.html.haml +2 -1
  25. data/app/views/tramway/layouts/application.html.haml +8 -6
  26. data/config/routes.rb +1 -2
  27. data/config/tailwind.config.js +57 -0
  28. data/lib/generators/tramway/install/font_awesome_helpers.rb +39 -0
  29. data/lib/generators/tramway/install/install_generator.rb +133 -66
  30. data/lib/generators/tramway/install/layout_helpers.rb +95 -0
  31. data/lib/tramway/configs/entities/page.rb +1 -0
  32. data/lib/tramway/engine.rb +12 -0
  33. data/lib/tramway/errors.rb +45 -0
  34. data/lib/tramway/forms/validations.rb +1 -2
  35. data/lib/tramway/helpers/navbar_helper.rb +3 -1
  36. data/lib/tramway/helpers/table_helper.rb +27 -0
  37. data/lib/tramway/helpers/views_helper.rb +26 -2
  38. data/lib/tramway/pg_searchable.rb +62 -0
  39. data/lib/tramway/searchable.rb +8 -26
  40. data/lib/tramway/version.rb +1 -1
  41. metadata +44 -4
@@ -6,6 +6,21 @@ module Tramway
6
6
  class HeaderComponent < Tramway::BaseComponent
7
7
  include ContentCells
8
8
 
9
+ SIZE_CLASSES = {
10
+ small: {
11
+ row: 'div-table-row grid grid-cols-1 gap-2 rounded-t-xl border-b border-zinc-800 bg-zinc-900 text-zinc-400',
12
+ cell: 'div-table-cell hidden px-4 py-2 first:block md:block'
13
+ },
14
+ medium: {
15
+ row: 'div-table-row grid grid-cols-1 gap-4 rounded-t-xl border-b border-zinc-800 bg-zinc-900 text-zinc-400',
16
+ cell: 'div-table-cell hidden px-6 py-4 first:block md:block'
17
+ },
18
+ large: {
19
+ row: 'div-table-row grid grid-cols-1 gap-6 rounded-t-xl border-b border-zinc-800 bg-zinc-900 text-zinc-400',
20
+ cell: 'div-table-cell hidden px-6 py-6 first:block md:block'
21
+ }
22
+ }.freeze
23
+
9
24
  option :headers, optional: true, default: -> { [] }
10
25
  option :columns, optional: true, default: -> { 3 }
11
26
  option :options, optional: true, default: -> { {} }
@@ -19,11 +34,17 @@ module Tramway
19
34
  end
20
35
 
21
36
  def header_row_classes
22
- 'div-table-row grid grid-cols-1 gap-4 rounded-t-xl border-b border-zinc-800 bg-zinc-900 text-zinc-400'
37
+ size_classes.fetch(:row)
23
38
  end
24
39
 
25
40
  def header_cell_classes
26
- 'div-table-cell hidden px-6 py-4 first:block md:block'
41
+ size_classes.fetch(:cell)
42
+ end
43
+
44
+ private
45
+
46
+ def size_classes
47
+ SIZE_CLASSES.fetch(tramway_table_size) { SIZE_CLASSES[:medium] }
27
48
  end
28
49
  end
29
50
  end
@@ -6,9 +6,20 @@ module Tramway
6
6
  class RowComponent < Tramway::BaseComponent
7
7
  include ContentCells
8
8
 
9
- DESKTOP_ROW_CLASSES = %w[
10
- div-table-row grid grid-cols-1 gap-4 border-b border-zinc-800 bg-zinc-950 last:border-b-0
11
- ].freeze
9
+ SIZE_CLASSES = {
10
+ small: {
11
+ row: 'div-table-row grid grid-cols-1 gap-2 border-b border-zinc-800 bg-transparent last:border-b-0',
12
+ cell: 'div-table-cell bg-transparent px-4 py-2 text-sm font-medium text-zinc-100 sm:text-sm'
13
+ },
14
+ medium: {
15
+ row: 'div-table-row grid grid-cols-1 gap-4 border-b border-zinc-800 bg-transparent last:border-b-0',
16
+ cell: 'div-table-cell bg-transparent px-6 py-4 text-xs font-medium text-zinc-100 sm:text-base'
17
+ },
18
+ large: {
19
+ row: 'div-table-row grid grid-cols-1 gap-6 border-b border-zinc-800 bg-transparent last:border-b-0',
20
+ cell: 'div-table-cell bg-transparent px-6 py-6 text-lg font-medium text-zinc-100'
21
+ }
22
+ }.freeze
12
23
 
13
24
  option :cells, optional: true, default: -> { [] }
14
25
  option :href, optional: true
@@ -32,7 +43,7 @@ module Tramway
32
43
  end
33
44
 
34
45
  def desktop_row_classes(cells_count)
35
- (DESKTOP_ROW_CLASSES + ["md:grid-cols-#{cells_count}"]).join(' ')
46
+ "#{size_classes.fetch(:row)} md:grid-cols-#{cells_count}"
36
47
  end
37
48
 
38
49
  def link_row_classes
@@ -40,25 +51,31 @@ module Tramway
40
51
  end
41
52
 
42
53
  def cell_classes
43
- 'div-table-cell px-6 py-4 text-xs font-medium text-zinc-100 sm:text-base'
54
+ size_classes.fetch(:cell)
44
55
  end
45
56
 
46
57
  def around_render
47
58
  ensure_view_context_accessor
48
- previous_flag = view_context.tramway_inside_cell
49
- view_context.tramway_inside_cell = href.present?
59
+ context = tramway_table_context
60
+ previous_flag = context.tramway_inside_cell
61
+ context.tramway_inside_cell = href.present?
50
62
 
51
63
  yield
52
64
  ensure
53
- view_context.tramway_inside_cell = previous_flag
65
+ context.tramway_inside_cell = previous_flag if context.respond_to?(:tramway_inside_cell=)
54
66
  end
55
67
 
56
68
  private
57
69
 
58
70
  def ensure_view_context_accessor
59
- return if view_context.respond_to?(:tramway_inside_cell=)
71
+ context = tramway_table_context
72
+ return if context.respond_to?(:tramway_inside_cell=)
60
73
 
61
- view_context.singleton_class.attr_accessor :tramway_inside_cell
74
+ context.singleton_class.attr_accessor :tramway_inside_cell
75
+ end
76
+
77
+ def size_classes
78
+ SIZE_CLASSES.fetch(tramway_table_size) { SIZE_CLASSES[:medium] }
62
79
  end
63
80
  end
64
81
  end
@@ -3,10 +3,37 @@
3
3
  module Tramway
4
4
  # Table component for rendering a table
5
5
  class TableComponent < Tramway::BaseComponent
6
+ TABLE_SIZES = %i[small medium large].freeze
7
+
8
+ option :size, default: -> { :medium }
6
9
  option :options, optional: true, default: -> { {} }
7
10
 
8
11
  def table_classes
9
- 'div-table w-full text-left rtl:text-right text-zinc-100'
12
+ 'div-table w-full bg-zinc-950 text-left rtl:text-right text-zinc-100'
13
+ end
14
+
15
+ def around_render
16
+ ensure_view_context_accessor
17
+ context = tramway_table_context
18
+ previous_size = context.tramway_table_size
19
+ context.tramway_table_size = normalized_size
20
+
21
+ yield
22
+ ensure
23
+ context.tramway_table_size = previous_size if context.respond_to?(:tramway_table_size=)
24
+ end
25
+
26
+ private
27
+
28
+ def normalized_size
29
+ TABLE_SIZES.include?(size) ? size : :medium
30
+ end
31
+
32
+ def ensure_view_context_accessor
33
+ context = tramway_table_context
34
+ return if context.respond_to?(:tramway_table_size=)
35
+
36
+ context.singleton_class.attr_accessor :tramway_table_size
10
37
  end
11
38
  end
12
39
  end
@@ -19,6 +19,7 @@ module Tramway
19
19
  model_class.order(id: :desc)
20
20
  end => entities
21
21
 
22
+ entities = preload(entities)
22
23
  entities = search(entities)
23
24
  entities = entities.page(params[:page])
24
25
  @entities = entities
@@ -28,7 +29,7 @@ module Tramway
28
29
 
29
30
  def show
30
31
  @record = tramway_decorate(
31
- model_class.find(params[:id]),
32
+ model_class.find(params.expect(:id)),
32
33
  namespace: entity.namespace
33
34
  ).with(view_context:)
34
35
 
@@ -40,14 +41,14 @@ module Tramway
40
41
  end
41
42
 
42
43
  def edit
43
- @record = tramway_form model_class.find(params[:id]), namespace: entity.namespace
44
+ @record = tramway_form model_class.find(params.expect(:id)), namespace: entity.namespace
44
45
  end
45
46
 
46
47
  # rubocop:disable Metrics/AbcSize
47
48
  def create
48
49
  @record = tramway_form model_class.new, namespace: entity.namespace
49
50
 
50
- if @record.submit params[model_class.model_name.param_key]
51
+ if @record.submit params.require(model_class.model_name.param_key).permit!
51
52
  redirect_to Tramway::Engine.routes.url_helpers.public_send(entity.show_helper_method, @record.id),
52
53
  notice: t('tramway.notices.created')
53
54
  else
@@ -56,9 +57,9 @@ module Tramway
56
57
  end
57
58
 
58
59
  def update
59
- @record = tramway_form model_class.find(params[:id]), namespace: entity.namespace
60
+ @record = tramway_form model_class.find(params.expect(:id)), namespace: entity.namespace
60
61
 
61
- if @record.submit params[model_class.model_name.param_key]
62
+ if @record.submit params.require(model_class.model_name.param_key).permit!
62
63
  redirect_to Tramway::Engine.routes.url_helpers.public_send(entity.show_helper_method, @record.id),
63
64
  notice: t('tramway.notices.updated')
64
65
  else
@@ -68,7 +69,7 @@ module Tramway
68
69
  # rubocop:enable Metrics/AbcSize
69
70
 
70
71
  def destroy
71
- @record = model_class.find(params[:id])
72
+ @record = model_class.find(params.expect(:id))
72
73
 
73
74
  @record.destroy
74
75
 
@@ -90,6 +91,12 @@ module Tramway
90
91
  entity.page(:index).scope
91
92
  end
92
93
 
94
+ def preload(entities)
95
+ includes = entity.page(:index).includes
96
+
97
+ includes.present? ? entities.includes(includes) : entities
98
+ end
99
+
93
100
  def set_associations
94
101
  @associations = @record.send(:__show_associations, params[:page])
95
102
  end
@@ -22,7 +22,8 @@
22
22
  - if @entity.page(:index).search
23
23
  = form_with url: public_send(custom_path_method), method: :get, local: true, builder: Tramway::Form::Builder, horizontal: true do |f|
24
24
  = f.text_field :query, label: false, value: params[:query], placeholder: t('tramway.actions.search')
25
- = f.submit t('tramway.actions.search')
25
+ = f.submit nil, 'aria-label' => t('tramway.actions.search') do
26
+ %i.fa.fa-search{ 'aria-hidden' => 'true' }
26
27
 
27
28
  = paginate @entities, custom_path_method:
28
29
 
@@ -14,6 +14,7 @@
14
14
  %link{rel: "apple-touch-icon", href: "/icon.png"}
15
15
 
16
16
  = stylesheet_link_tag "tailwind", "data-turbo-track": "reload"
17
+ = stylesheet_link_tag "font-awesome", "data-turbo-track": "reload"
17
18
  = stylesheet_link_tag "trix", "data-turbo-track": "reload"
18
19
  = javascript_include_tag "trix", "data-turbo-track": "reload", defer: true
19
20
  = javascript_importmap_tags
@@ -21,10 +22,11 @@
21
22
  %body.bg-zinc-950.text-zinc-50
22
23
  = tramway_navbar title: 'Tramway'
23
24
 
24
- - if flash.any?
25
- = tramway_flash text: flash[:notice].presence || flash[:alert],
26
- type: flash[:notice].present? ? :will : :rage,
27
- id: 'flash-container'
25
+ = tramway_main_container id: 'tramway-main-container', class: 'md:pl-72 transition-[padding-left] duration-300 ease-in-out-strong motion-reduce:duration-0' do
26
+ - if flash.any?
27
+ = tramway_flash text: flash[:notice].presence || flash[:alert],
28
+ type: flash[:notice].present? ? :will : :rage,
29
+ id: 'flash-container'
28
30
 
29
- .container.mx-auto.p-4.flex.align-center.justify-center
30
- = yield
31
+ .container.mx-auto.p-4.flex.align-center.justify-center
32
+ = yield
data/config/routes.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'tramway/helpers/routes_helper'
4
4
 
5
- # rubocop:disable Metrics/BlockLength
5
+ # rubocop:disable-next Metrics/BlockLength
6
6
  Tramway::Engine.routes.draw do
7
7
  Tramway.config.entities.each do |entity|
8
8
  if entity.namespace.present?
@@ -55,7 +55,6 @@ Tramway::Engine.routes.draw do
55
55
  end
56
56
  end
57
57
  end
58
- # rubocop:enable Metrics/BlockLength
59
58
 
60
59
  Tramway::Engine.routes.routes.map(&:name).compact.each do |route_name|
61
60
  %w[path url].each do |suffix|
@@ -1,5 +1,8 @@
1
1
  const defaultTheme = require("tailwindcss/defaultTheme")
2
2
 
3
+ const buildUtilities = (prefix, start, end) =>
4
+ Array.from({ length: end - start + 1 }, (_, index) => `${prefix}${start + index}`)
5
+
3
6
  module.exports = {
4
7
  safelist: [
5
8
  // === Navbar ===
@@ -13,17 +16,52 @@ module.exports = {
13
16
  'md:translate-x-0',
14
17
  'border-white',
15
18
  'h-screen',
19
+ 'h-dvh',
16
20
  'w-screen',
21
+ 'w-72',
17
22
  'z-50',
18
23
  'inset-0',
24
+ 'top-0',
19
25
  'top-4',
20
26
  'right-6',
27
+ 'right-0',
21
28
  'text-4xl',
22
29
  'transition-transform',
23
30
  '-translate-x-full',
24
31
  'duration-300',
25
32
  'space-y-3',
26
33
  'md:justify-between',
34
+ 'hidden',
35
+ 'md:fixed',
36
+ 'md:left-0',
37
+ 'md:top-0',
38
+ 'md:z-40',
39
+ 'md:h-dvh',
40
+ 'md:w-72',
41
+ 'md:w-24',
42
+ 'md:flex-col',
43
+ 'md:hidden',
44
+ 'md:border-r',
45
+ 'md:border-zinc-800',
46
+ 'md:bg-zinc-950',
47
+ 'md:px-4',
48
+ 'md:py-3',
49
+ 'md:shadow-sm',
50
+ 'md:backdrop-blur',
51
+ 'md:overflow-hidden',
52
+ 'md:pl-72',
53
+ 'md:pl-24',
54
+ 'transition-all',
55
+ 'ease-in-out',
56
+ 'md:transition-[width]',
57
+ 'transition-[padding-left]',
58
+ 'ease-in-out-strong',
59
+ 'md:ease-in-out-strong',
60
+ 'ease-out-strong',
61
+ 'md:motion-reduce:duration-0',
62
+ 'motion-reduce:duration-0',
63
+ 'duration-200',
64
+ 'rotate-180',
27
65
  'pt-16',
28
66
  'min-h-8',
29
67
  'bg-zinc-950',
@@ -55,6 +93,7 @@ module.exports = {
55
93
  'shadow-lg',
56
94
  'shadow-sm',
57
95
  'w-fit',
96
+ 'text-xs',
58
97
  'text-base',
59
98
  'text-sm',
60
99
  'text-lg',
@@ -64,11 +103,23 @@ module.exports = {
64
103
  'border-r',
65
104
  'border-t',
66
105
  'border',
106
+ 'border-dotted',
107
+ 'border-zinc-700/50',
108
+ 'border-zinc-100/60',
109
+ 'box-border',
67
110
  'gap-4',
111
+ 'gap-6',
68
112
  'gap-1',
69
113
  'gap-2',
70
114
  'grid-cols-1',
71
115
  'grid',
116
+ 'overflow-auto',
117
+ 'inset-x-0',
118
+ 'inset-y-0',
119
+ ...buildUtilities('md:grid-cols-', 1, 12),
120
+ ...buildUtilities('md:grid-rows-', 1, 12),
121
+ ...buildUtilities('md:col-span-', 1, 12),
122
+ ...buildUtilities('md:row-span-', 1, 12),
72
123
  'px-4',
73
124
  'px-3',
74
125
  'py-3',
@@ -79,6 +130,7 @@ module.exports = {
79
130
  'inset-0',
80
131
  'top-4',
81
132
  'right-4',
133
+ 'z-10',
82
134
  'flex-col',
83
135
  'items-center',
84
136
  'justify-between',
@@ -173,6 +225,7 @@ module.exports = {
173
225
  'sm:px-3',
174
226
  'sm:py-2',
175
227
  'sm:text-sm',
228
+ 'sm:text-base',
176
229
  'md:gap-1',
177
230
  'md:space-y-4',
178
231
  'space-y-2',
@@ -651,6 +704,10 @@ module.exports = {
651
704
  fontFamily: {
652
705
  sans: ["Inter var", ...defaultTheme.fontFamily.sans],
653
706
  },
707
+ transitionTimingFunction: {
708
+ "out-strong": "cubic-bezier(0.23, 1, 0.32, 1)",
709
+ "in-out-strong": "cubic-bezier(0.77, 0, 0.175, 1)",
710
+ },
654
711
  keyframes: {
655
712
  "accordion-down": {
656
713
  from: { height: 0 },
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway
4
+ module Generators
5
+ # nodoc
6
+ module InstallGeneratorFontAwesomeHelpers
7
+ private
8
+
9
+ def font_awesome_already_present?(content)
10
+ content.include?('stylesheet_link_tag "font-awesome"') ||
11
+ content.include?("stylesheet_link_tag 'font-awesome'")
12
+ end
13
+
14
+ def ensure_font_awesome_in_haml_layout
15
+ content = File.read(application_layout_haml_path)
16
+ return if font_awesome_already_present?(content)
17
+ return unless content.match?(/^\s+= stylesheet_link_tag "tailwind", "data-turbo-track": "reload"/)
18
+
19
+ updated = content.sub(
20
+ /^(\s+= stylesheet_link_tag "tailwind", "data-turbo-track": "reload"\n)/,
21
+ "\\1 = stylesheet_link_tag \"font-awesome\", \"data-turbo-track\": \"reload\"\n"
22
+ )
23
+ File.write(application_layout_haml_path, updated)
24
+ end
25
+
26
+ def ensure_font_awesome_in_erb_layout
27
+ content = File.read(application_layout_erb_path)
28
+ return if font_awesome_already_present?(content)
29
+ return unless content.include?('</head>')
30
+
31
+ updated = content.sub(
32
+ /(\s+<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>\n)/,
33
+ "\\1 <%= stylesheet_link_tag \"font-awesome\", \"data-turbo-track\": \"reload\" %>\n"
34
+ )
35
+ File.write(application_layout_erb_path, updated)
36
+ end
37
+ end
38
+ end
39
+ end