maglevcms 3.0.0.beta2 → 3.0.0

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -53
  3. data/Rakefile +3 -1
  4. data/app/assets/builds/maglev/tailwind.css +681 -11670
  5. data/app/assets/config/maglev_manifest.js +3 -2
  6. data/app/assets/javascripts/maglev/client/dom-operations.js +5 -5
  7. data/app/assets/javascripts/maglev/client/iframe-decorator.js +24 -0
  8. data/app/assets/javascripts/maglev/client/incoming-messages.js +13 -3
  9. data/app/assets/javascripts/maglev/client/index.js +3 -2
  10. data/app/assets/javascripts/maglev/client/utils.js +22 -0
  11. data/app/assets/javascripts/maglev/editor/controllers/app/forms/section_form_controller.js +4 -3
  12. data/app/assets/javascripts/maglev/editor/controllers/app/forms/style_form_controller.js +2 -1
  13. data/app/assets/javascripts/maglev/editor/controllers/app/page_preview_controller.js +96 -5
  14. data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js +105 -23
  15. data/app/assets/javascripts/maglev/editor/controllers/app/setting_controller.js +3 -2
  16. data/app/assets/javascripts/maglev/editor/controllers/shared/copy_to_clipboard_controller.js +2 -1
  17. data/app/assets/javascripts/maglev/editor/controllers/shared/submit_button_controller.js +29 -5
  18. data/app/assets/javascripts/maglev/editor/controllers/utils.js +38 -0
  19. data/app/assets/javascripts/maglev/editor/index.js +5 -44
  20. data/app/assets/javascripts/maglev/editor/patches/page_renderer_patch.js +47 -0
  21. data/app/assets/javascripts/maglev/editor/patches/turbo_delayed_streams.js +35 -0
  22. data/app/assets/javascripts/maglev/editor/patches/turbo_stream_patch.js +50 -0
  23. data/app/assets/stylesheets/maglev/application.css +0 -2
  24. data/app/assets/stylesheets/maglev/tailwind.css.erb +11 -2
  25. data/app/components/maglev/content/link.rb +1 -1
  26. data/app/components/maglev/editor/settings/link/link_component.rb +7 -1
  27. data/app/components/maglev/section_component.rb +11 -1
  28. data/app/components/maglev/uikit/app_layout/sidebar/link_component.html.erb +1 -1
  29. data/app/components/maglev/uikit/app_layout/sidebar/link_component.rb +35 -5
  30. data/app/components/maglev/uikit/app_layout/sidebar_component.html.erb +3 -4
  31. data/app/components/maglev/uikit/app_layout/topbar/logo_component.html.erb +1 -1
  32. data/app/components/maglev/uikit/app_layout/topbar/page_info_component.html.erb +1 -1
  33. data/app/components/maglev/uikit/app_layout/topbar_component.html.erb +1 -1
  34. data/app/components/maglev/uikit/app_layout/topbar_component.rb +1 -1
  35. data/app/components/maglev/uikit/button_group_component/button_group_component.html.erb +5 -0
  36. data/app/components/maglev/uikit/button_group_component.rb +70 -0
  37. data/app/components/maglev/uikit/device_toggler_component.rb +10 -1
  38. data/app/components/maglev/uikit/dropdown_component/dropdown_component.html.erb +2 -2
  39. data/app/components/maglev/uikit/dropdown_component/dropdown_controller.js +6 -1
  40. data/app/components/maglev/uikit/dropdown_component.rb +6 -1
  41. data/app/components/maglev/uikit/form/color_field_component.html.erb +1 -1
  42. data/app/components/maglev/uikit/form/combobox_component.html.erb +1 -0
  43. data/app/components/maglev/uikit/form/link_component.rb +5 -1
  44. data/app/components/maglev/uikit/form/richtext_controller.js +5 -4
  45. data/app/components/maglev/uikit/form/search_form_component.html.erb +1 -0
  46. data/app/components/maglev/uikit/icon_component.rb +3 -0
  47. data/app/components/maglev/uikit/image_library/uploader_controller.js +3 -2
  48. data/app/components/maglev/uikit/list/list_item_component.html.erb +36 -19
  49. data/app/components/maglev/uikit/list/list_item_component.rb +19 -5
  50. data/app/components/maglev/uikit/locale_switcher_component/locale_switcher_component.html.erb +6 -10
  51. data/app/components/maglev/uikit/menu_dropdown_component/menu_dropdown_component.html.erb +6 -2
  52. data/app/components/maglev/uikit/menu_dropdown_component.rb +244 -7
  53. data/app/components/maglev/uikit/page_actions_dropdown_component/page_actions_dropdown_component.html.erb +39 -46
  54. data/app/components/maglev/uikit/pagination_component/pagination_component.html.erb +9 -12
  55. data/app/components/maglev/uikit/pagination_component.rb +6 -1
  56. data/app/components/maglev/uikit/section_toolbar/bottom_component.html.erb +1 -1
  57. data/app/components/maglev/uikit/tabs_component/tabs_component.html.erb +7 -4
  58. data/app/components/maglev/uikit/tabs_component.rb +23 -4
  59. data/app/components/maglev/uikit/well/simple_well_component.html.erb +15 -0
  60. data/app/components/maglev/uikit/well/simple_well_component.rb +13 -0
  61. data/app/controllers/concerns/maglev/editor/errors_concern.rb +9 -9
  62. data/app/controllers/concerns/maglev/editor/preview_urls_concern.rb +32 -0
  63. data/app/controllers/concerns/maglev/editor/turbo_concern.rb +29 -0
  64. data/app/controllers/concerns/maglev/errors_concern.rb +17 -0
  65. data/app/controllers/concerns/maglev/flash_i18n_concern.rb +1 -0
  66. data/app/controllers/maglev/application_controller.rb +2 -1
  67. data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +2 -1
  68. data/app/controllers/maglev/editor/assets_controller.rb +1 -1
  69. data/app/controllers/maglev/editor/base_controller.rb +6 -32
  70. data/app/controllers/maglev/editor/pages/clone_controller.rb +22 -0
  71. data/app/controllers/maglev/editor/pages/discard_draft_controller.rb +17 -0
  72. data/app/controllers/maglev/editor/pages_controller.rb +26 -7
  73. data/app/controllers/maglev/editor/section_blocks_controller.rb +13 -9
  74. data/app/controllers/maglev/editor/sections_controller.rb +26 -7
  75. data/app/controllers/maglev/published_page_preview_controller.rb +4 -0
  76. data/app/controllers/maglev/site_controller.rb +15 -0
  77. data/app/helpers/maglev/application_helper.rb +26 -8
  78. data/app/helpers/maglev/editor/section_blocks_helper.rb +2 -2
  79. data/app/models/maglev/page/publishable_concern.rb +69 -0
  80. data/app/models/maglev/page.rb +3 -0
  81. data/app/models/maglev/section/block.rb +4 -0
  82. data/app/models/maglev/section/content_concern.rb +3 -1
  83. data/app/models/maglev/section.rb +21 -1
  84. data/app/models/maglev/sections_content_store.rb +1 -3
  85. data/app/models/maglev/site.rb +5 -0
  86. data/app/services/concerns/maglev/content/helpers_concern.rb +5 -8
  87. data/app/services/maglev/app_container.rb +4 -2
  88. data/app/services/maglev/discard_page_draft_service.rb +45 -0
  89. data/app/services/maglev/fetch_section_screenshot_url.rb +12 -1
  90. data/app/services/maglev/fetch_site.rb +3 -1
  91. data/app/services/maglev/get_published_page_sections_service.rb +1 -1
  92. data/app/services/maglev/has_unpublished_changes.rb +21 -0
  93. data/app/services/maglev/publish_service.rb +18 -2
  94. data/app/views/layouts/maglev/editor/_sidebar.html.erb +8 -2
  95. data/app/views/layouts/maglev/editor/_topbar.html.erb +6 -23
  96. data/app/views/layouts/maglev/editor/application.html.erb +6 -0
  97. data/app/views/layouts/maglev/editor/topbar/_page_info.html.erb +11 -0
  98. data/app/views/layouts/maglev/editor/topbar/_publish_button.html.erb +35 -0
  99. data/app/views/maglev/editor/assets/index.html.erb +1 -0
  100. data/app/views/maglev/editor/home/index.html.erb +1 -1
  101. data/app/views/maglev/editor/links/edit/_email.html.erb +1 -1
  102. data/app/views/maglev/editor/links/edit/_url.html.erb +1 -1
  103. data/app/views/maglev/editor/pages/_list.html.erb +25 -21
  104. data/app/views/maglev/editor/pages/_preview.html.erb +6 -6
  105. data/app/views/maglev/editor/pages/_preview_empty_message.html.erb +13 -10
  106. data/app/views/maglev/editor/pages/discard_draft/create.turbo_stream.erb +16 -0
  107. data/app/views/maglev/editor/pages/index.html.erb +8 -1
  108. data/app/views/maglev/editor/publication/create.turbo_stream.erb +3 -1
  109. data/app/views/maglev/editor/section_blocks/_form.html.erb +5 -13
  110. data/app/views/maglev/editor/section_blocks/_form_with_tabs.html.erb +15 -0
  111. data/app/views/maglev/editor/section_blocks/_new.html.erb +1 -1
  112. data/app/views/maglev/editor/section_blocks/edit.html.erb +2 -2
  113. data/app/views/maglev/editor/section_blocks/index/_list.html.erb +2 -2
  114. data/app/views/maglev/editor/section_blocks/index/_tree.html.erb +1 -1
  115. data/app/views/maglev/editor/section_blocks/update.turbo_stream.erb +9 -1
  116. data/app/views/maglev/editor/sections/_form.html.erb +6 -20
  117. data/app/views/maglev/editor/sections/_form_with_tabs.html.erb +21 -0
  118. data/app/views/maglev/editor/sections/_list.html.erb +1 -1
  119. data/app/views/maglev/editor/sections/edit.html.erb +3 -3
  120. data/app/views/maglev/editor/sections/index.html.erb +1 -1
  121. data/app/views/maglev/editor/sections/new.html.erb +18 -1
  122. data/app/views/maglev/editor/sections/theme/_empty_list.html.erb +3 -0
  123. data/app/views/maglev/editor/sections/theme/_list.html.erb +35 -0
  124. data/app/views/maglev/editor/sections/theme/_screenshot_placeholder.html.erb +6 -0
  125. data/app/views/maglev/editor/sections/theme/_search.html.erb +22 -0
  126. data/app/views/maglev/editor/sections/update.turbo_stream.erb +9 -1
  127. data/app/views/maglev/editor/shared/_button_label.html.erb +4 -4
  128. data/app/views/maglev/editor/style/edit.html.erb +1 -0
  129. data/app/views/maglev/errors/site_not_found.html.erb +33 -0
  130. data/config/editor_importmap.rb +16 -13
  131. data/config/locales/editor.ar.yml +12 -4
  132. data/config/locales/editor.en.yml +31 -23
  133. data/config/locales/editor.es.yml +12 -4
  134. data/config/locales/editor.fr.yml +12 -4
  135. data/config/locales/editor.pt-BR.yml +12 -4
  136. data/config/routes/maglev/assets.rb +4 -0
  137. data/config/routes/maglev/editor.rb +38 -0
  138. data/config/routes/maglev/preview.rb +8 -0
  139. data/config/routes/maglev/public_preview.rb +6 -0
  140. data/config/routes.rb +8 -44
  141. data/db/migrate/20211013210954_translate_section_content.rb +1 -0
  142. data/db/migrate/20251116171603_add_published_at_to_sites_and_pages.rb +6 -0
  143. data/db/migrate/20260114112058_add_published_payload_to_pages.rb +14 -0
  144. data/exe/tailwind-cli +1 -1
  145. data/lib/generators/maglev/install_generator.rb +9 -7
  146. data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +10 -3
  147. data/lib/maglev/active_storage/serving_blob.rb +29 -0
  148. data/lib/maglev/active_storage.rb +2 -0
  149. data/lib/maglev/config.rb +22 -3
  150. data/lib/maglev/engine.rb +15 -10
  151. data/lib/maglev/errors.rb +1 -0
  152. data/lib/maglev/version.rb +1 -1
  153. data/lib/maglev.rb +18 -3
  154. data/lib/tasks/db_test_all.rake +290 -0
  155. data/lib/tasks/maglev/tailwindcss.rake +1 -0
  156. metadata +55 -19
  157. data/app/controllers/maglev/editor/page_clone_controller.rb +0 -20
  158. data/app/views/maglev/editor/sections/_theme_list.html.erb +0 -32
  159. /data/vendor/javascript/{@floating-ui--core.js → maglev/@floating-ui--core.js} +0 -0
  160. /data/vendor/javascript/{@floating-ui--dom.js → maglev/@floating-ui--dom.js} +0 -0
  161. /data/vendor/javascript/{@floating-ui--utils--dom.js → maglev/@floating-ui--utils--dom.js} +0 -0
  162. /data/vendor/javascript/{@floating-ui--utils.js → maglev/@floating-ui--utils.js} +0 -0
  163. /data/vendor/javascript/{@hotwired--stimulus.js → maglev/@hotwired--stimulus.js} +0 -0
  164. /data/vendor/javascript/{@hotwired--turbo-rails.js → maglev/@hotwired--turbo-rails.js} +0 -0
  165. /data/vendor/javascript/{@hotwired--turbo.js → maglev/@hotwired--turbo.js} +0 -0
  166. /data/vendor/javascript/{@rails--actioncable--src.js → maglev/@rails--actioncable--src.js} +0 -0
  167. /data/vendor/javascript/{@rails--request.js.js → maglev/@rails--request.js.js} +0 -0
  168. /data/vendor/javascript/{@shopify--draggable.js → maglev/@shopify--draggable.js} +0 -0
  169. /data/vendor/javascript/{el-transition.js → maglev/el-transition.js} +0 -0
  170. /data/vendor/javascript/{stimulus-use.js → maglev/stimulus-use.js} +0 -0
  171. /data/vendor/javascript/{tiptap.bundle.js → maglev/tiptap.bundle.js} +0 -0
@@ -47,7 +47,12 @@ export default class extends Controller {
47
47
  }
48
48
 
49
49
  disconnect() {
50
- this.cleanup()
50
+ this.cleanup() // clean up the observer for the floating element
51
+
52
+ // fix issue: https://github.com/stimulus-use/stimulus-use/issues/500
53
+ this.enter = null
54
+ this.leave = null
55
+ this.toggleTransition = null
51
56
  }
52
57
 
53
58
  clickOutside() {
@@ -7,9 +7,14 @@ module Maglev
7
7
 
8
8
  attr_reader :placement
9
9
 
10
- def initialize(placement: 'bottom-start')
10
+ def initialize(placement: 'bottom-start', wrapper_classes: nil)
11
+ @wrapper_classes = wrapper_classes
11
12
  @placement = placement
12
13
  end
14
+
15
+ def wrapper_classes
16
+ helpers.class_names('relative', @wrapper_classes)
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -50,7 +50,7 @@
50
50
  ) %>
51
51
  <% end %>
52
52
 
53
- <div class="flex flex-row flex-wrap gap-2">
53
+ <div class="flex flex-row flex-wrap gap-2 p-2">
54
54
  <% presets.each do |preset| %>
55
55
  <%= tag.button(
56
56
  type: 'button',
@@ -33,6 +33,7 @@
33
33
  placeholder: placeholder || t('maglev.uikit.form.combobox.placeholder'),
34
34
  value: selected_label,
35
35
  aria: { controls: 'options', expanded: 'false' },
36
+ autocomplete: 'off',
36
37
  data: {
37
38
  'uikit-form-combobox-target' => 'input',
38
39
  'uikit-dropdown-target' => 'button',
@@ -59,7 +59,7 @@ module Maglev
59
59
 
60
60
  def self.link_type_component(input_name:, value:, path:)
61
61
  # Default to url if the link type is not found (shouldn't happen)
62
- klass = link_type_klasses_map.fetch(value[:link_type].to_sym, 'url')
62
+ klass = link_type_klasses_map.fetch(value[:link_type]&.to_sym, default_link_type_klass)
63
63
 
64
64
  return unless klass
65
65
 
@@ -74,6 +74,10 @@ module Maglev
74
74
  static_page: Maglev::Uikit::Form::Link::StaticPageLinkComponent
75
75
  }
76
76
  end
77
+
78
+ def self.default_link_type_klass
79
+ Maglev::Uikit::Form::Link::UrlLinkComponent
80
+ end
77
81
  end
78
82
  end
79
83
  end
@@ -1,5 +1,6 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import { Editor, Document, Text, Paragraph, Heading, Blockquote, CodeBlock, History, Bold, Italic, Underline, Strike, Superscript, HardBreak, Link, ListItem, BulletList, OrderedList } from 'tiptap'
3
+ import { log } from 'maglev-controllers/utils'
3
4
 
4
5
  const MaglevLink = Link.extend({
5
6
  addAttributes() {
@@ -205,11 +206,11 @@ export default class extends Controller {
205
206
  }
206
207
 
207
208
  openLinkModal(event) {
208
- console.log('toggleLink', event)
209
+ log('toggleLink', event)
209
210
  const url = new URL(this.editLinkPathValue, window.location.origin)
210
211
  const link = this.editor.getAttributes('link')
211
212
 
212
- console.log('toggleLink,build link from', link)
213
+ log('toggleLink,build link from', link)
213
214
 
214
215
  url.searchParams.set('input_name', this.inputNameValue)
215
216
  url.searchParams.set('link[href]', link.href ?? '')
@@ -230,8 +231,8 @@ export default class extends Controller {
230
231
  }
231
232
 
232
233
  setLink(event) {
233
- const link = JSON.parse(event.detail)
234
- console.log('setLink, link=', link)
234
+ const link = typeof event.detail === 'string' ? JSON.parse(event.detail) : event.detail
235
+ log('setLink, link=', link)
235
236
  this.editor.commands.setLink({
236
237
  href: link.href,
237
238
  target: link.open_new_window ? '_blank' : '',
@@ -14,6 +14,7 @@
14
14
  value: value,
15
15
  placeholder: placeholder,
16
16
  aria_label: placeholder,
17
+ autocomplete: 'off',
17
18
  class: 'block w-full border-0 bg-transparent focus:outline-none placeholder-gray-500',
18
19
  data: { uikit_search_form_target: 'input' }
19
20
  ) %>
@@ -68,6 +68,7 @@ module Maglev
68
68
  arrow_down: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="fill-current"><path fill="none" d="M0 0h24v24H0z"></path><path d="m12 13.172 4.95-4.95 1.414 1.414L12 16 5.636 9.636 7.05 8.222z"></path></svg>',
69
69
  arrow_left: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M10.8284 12.0007L15.7782 16.9504L14.364 18.3646L8 12.0007L14.364 5.63672L15.7782 7.05093L10.8284 12.0007Z"></path></svg>',
70
70
  arrow_right: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.1717 12.0007L8.22192 7.05093L9.63614 5.63672L16.0001 12.0007L9.63614 18.3646L8.22192 16.9504L13.1717 12.0007Z"></path></svg>',
71
+ arrow_right_long: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M1.99974 13.0001L1.9996 11.0002L18.1715 11.0002L14.2218 7.05044L15.636 5.63623L22 12.0002L15.636 18.3642L14.2218 16.9499L18.1716 13.0002L1.99974 13.0001Z"></path></svg>',
71
72
 
72
73
  image: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4.828 21l-.02.02-.021-.02H2.992A.993.993 0 0 1 2 20.007V3.993A1 1 0 0 1 2.992 3h18.016c.548 0 .992.445.992.993v16.014a1 1 0 0 1-.992.993H4.828zM20 15V5H4v14L14 9l6 6zm0 2.828l-6-6L6.828 19H20v-1.172zM8 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z" fill="#000"/></svg>',
73
74
 
@@ -131,6 +132,8 @@ module Maglev
131
132
 
132
133
  logout: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 22a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v3h-2V4H6v16h12v-2h2v3a1 1 0 0 1-1 1H5zm13-6v-3h-7v-2h7V8l5 4-5 4z"/></svg>',
133
134
 
135
+ history: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12H4C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C9.25022 4 6.82447 5.38734 5.38451 7.50024L8 7.5V9.5H2V3.5H4L3.99989 5.99918C5.82434 3.57075 8.72873 2 12 2ZM13 7L12.9998 11.585L16.2426 14.8284L14.8284 16.2426L10.9998 12.413L11 7H13Z"></path></svg>',
136
+
134
137
  # Spinners
135
138
  spinner: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18.364 5.63604L16.9497 7.05025C15.683 5.7835 13.933 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12H21C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C14.4853 3 16.7353 4.00736 18.364 5.63604Z"></path></svg>'
136
139
  }.with_indifferent_access.freeze
@@ -1,5 +1,6 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
  import { FetchRequest } from '@rails/request.js'
3
+ import { log } from 'maglev-controllers/utils'
3
4
 
4
5
  export default class extends Controller {
5
6
  static targets = ['button', 'fileInput']
@@ -11,7 +12,7 @@ export default class extends Controller {
11
12
  }
12
13
 
13
14
  connect() {
14
- console.log('UploaderController connected')
15
+ log('UploaderController connected')
15
16
  }
16
17
 
17
18
  openFileDialog() {
@@ -64,7 +65,7 @@ export default class extends Controller {
64
65
  }
65
66
 
66
67
  reloadFrameTag() {
67
- console.log('reloadFrameTag', this.refreshPathValue)
68
+ log('reloadFrameTag', this.refreshPathValue)
68
69
  const frame = document.querySelector(`turbo-frame#modal-dialog`)
69
70
  if (frame) {
70
71
  frame.src = this.refreshPathValue
@@ -1,29 +1,48 @@
1
1
  <%= tag.div(data: { sortable_target: sortable_target, item_id: id, item_index: index }) do %>
2
- <%= tag.div(class: wrapper_classes) do %>
2
+ <%= content_tag(wrapper_tag_name.to_sym, class: wrapper_classes) do %>
3
+ <%= content if content? %>
3
4
 
4
5
  <%= tag.div(class: 'flex items-center cursor-move', data: { sortable_target: sortable_handle }) do %>
5
6
  <%= render Maglev::Uikit::IconComponent.new(name: "draggable") %>
6
7
  <% end if handle? %>
7
8
 
8
9
  <% item_body = capture do %>
9
- <% if image? %>
10
- <div class="h-8 w-8 bg-gray-400">
11
- <%= image %>
10
+ <% if icon? %>
11
+ <div class="grid grid-cols-[auto_1fr_auto] flex flex-1 items-center">
12
+ <div class="mr-2">
13
+ <%= icon %>
14
+ </div>
15
+ <div class="text-gray-800 truncate"><%= title %></div>
16
+ <% if sub_title? %>
17
+ <div class="col-start-2 text-gray-500 text-xs"><%= sub_title %></div>
18
+ <% end %>
12
19
  </div>
13
- <% end %>
14
-
15
- <% if big_image? %>
16
- <%= big_image %>
17
- <% end %>
20
+ <% else %>
21
+ <% if image? %>
22
+ <div class="h-8 w-8 bg-gray-400">
23
+ <%= image %>
24
+ </div>
25
+ <% end %>
18
26
 
19
- <div class="flex flex-col overflow-hidden">
20
- <% if pre_title? %>
21
- <div class="text-gray-500 text-xs"><%= pre_title %></div>
22
- <div class="text-gray-800 truncate"><%= title %></div>
23
- <% else %>
24
- <div class="truncate"><%= title %></div>
27
+ <% if big_image? %>
28
+ <%= big_image %>
25
29
  <% end %>
26
- </div>
30
+
31
+ <div class="flex flex-col overflow-hidden">
32
+ <% if pre_title? %>
33
+ <div class="text-gray-500 text-xs"><%= pre_title %></div>
34
+ <div class="text-gray-800 truncate"><%= title %></div>
35
+ <% if sub_title? %>
36
+ <div class="text-gray-500 text-xs"><%= sub_title %></div>
37
+ <% end %>
38
+ <% else %>
39
+ <div class="truncate"><%= title %></div>
40
+ <% if sub_title? %>
41
+ <div class="text-gray-500 text-xs"><%= sub_title %></div>
42
+ <% end %>
43
+ <% end %>
44
+ </div>
45
+ <% end %>
27
46
  <% end %>
28
47
 
29
48
  <% if link? %>
@@ -41,7 +60,5 @@
41
60
  <%= action %>
42
61
  </div>
43
62
  <% end %>
44
- <% end %>
45
-
46
- <%= content if content? %>
63
+ <% end %>
47
64
  <% end %>
@@ -7,16 +7,20 @@ module Maglev
7
7
  renders_one :handle
8
8
  renders_one :image
9
9
  renders_one :big_image
10
+ renders_one :icon
10
11
  renders_one :pre_title
12
+ renders_one :sub_title
11
13
  renders_one :title
12
14
  renders_one :action
13
15
 
14
- attr_reader :link, :index, :options
16
+ attr_reader :link, :index, :options, :variant, :wrapper_tag_name
15
17
 
16
18
  def initialize(id: nil, link: nil, options: {})
17
19
  @id = id
18
20
  @link = link
21
+ @variant = options.fetch(:variant, :filled).to_sym
19
22
  @custom_wrapper_classes = options[:wrapper_classes]
23
+ @wrapper_tag_name = options[:wrapper_tag] || :div
20
24
  @index = options[:index]
21
25
  @options = options
22
26
  end
@@ -37,18 +41,28 @@ module Maglev
37
41
  link[:data] || {}
38
42
  end
39
43
 
44
+ # rubocop:disable Metrics/MethodLength
40
45
  def wrapper_classes
41
46
  class_variants(
42
- base: 'bg-gray-100 rounded-md px-2 flex text-gray-800'
43
- ).render(class: @custom_wrapper_classes)
47
+ base: 'rounded-md flex text-gray-800',
48
+ variants: {
49
+ variant: {
50
+ filled: 'bg-gray-100',
51
+ ghost: 'hover:bg-gray-50 transition-colors duration-200'
52
+ },
53
+ padding: { default: 'p-2', medium: 'p-3' }
54
+ },
55
+ default: { variant: :filled, padding: :default }
56
+ ).render(variant: variant, padding: big_image? ? :medium : :default, class: @custom_wrapper_classes)
44
57
  end
58
+ # rubocop:enable Metrics/MethodLength
45
59
 
46
60
  def content_classes
47
61
  class_variants(
48
- base: 'flex flex-1 py-3 gap-3 overflow-hidden px-2',
62
+ base: 'flex flex-1 gap-3 overflow-hidden',
49
63
  variants: {
50
64
  disposition: {
51
- row: 'flex-row items-center',
65
+ row: 'flex-row items-center px-2',
52
66
  col: 'flex-col'
53
67
  }
54
68
  },
@@ -1,22 +1,18 @@
1
1
  <%= render(Maglev::Uikit::MenuDropdownComponent.new) do |dropdown| %>
2
2
  <% dropdown.with_trigger do %>
3
- <button
4
- class="flex-1 h-full w-full px-6 hover:bg-gray-100 transition-colors duration-200 flex items-center focus:outline-none focus:none cursor-pointer space-x-2"
5
- data-action="click->uikit-dropdown#toggle"
6
- data-uikit-dropdown-target="button">
3
+ <%= tag.button class: helpers.maglev_button_classes(color: :secondary, class: 'space-x-2'), data: { action: 'click->uikit-dropdown#toggle', 'uikit-dropdown-target': 'button' } do %>
7
4
  <%= render Maglev::Uikit::IconComponent.new(name: 'global') %>
8
5
  <span class="whitespace-nowrap">
9
6
  <%= current_locale_label %>
10
7
  </span>
11
8
  <%= render Maglev::Uikit::IconComponent.new(name: 'arrow_down') %>
12
- </button>
9
+ <% end %>
13
10
  <% end %>
14
11
 
15
12
  <% locales.each do |locale| %>
16
- <% dropdown.with_item do %>
17
- <%= link_to locale[:path], class: dropdown.item_classes, data: locale[:data_attributes] do %>
18
- <span class="<%= 'font-bold' if locale[:value] == current_locale %>"><%= locale[:label] %></span>
19
- <% end %>
20
- <% end %>
13
+ <% dropdown.with_item_link_to(locale[:path], data: locale[:data_attributes]) do |menu_item| %>
14
+ <% menu_item.with_label_content(locale[:label]) %>
15
+ <% menu_item.with_icon_content('check') if locale[:value] == current_locale %>
16
+ <% end %>
21
17
  <% end %>
22
18
  <% end %>
@@ -1,13 +1,17 @@
1
- <%= render Maglev::Uikit::DropdownComponent.new(placement: placement) do |component| %>
1
+ <%= render Maglev::Uikit::DropdownComponent.new(placement: placement, wrapper_classes: wrapper_classes) do |component| %>
2
2
  <% component.with_trigger do %>
3
3
  <% if trigger.present? %>
4
4
  <%= trigger %>
5
+ <% elsif trigger_classes.present? && icon_name.present? %>
6
+ <%= button_tag class: trigger_classes, data: { action: "click->uikit-dropdown#toggle", 'uikit-dropdown-target': 'button' } do %>
7
+ <%= render Maglev::Uikit::IconComponent.new(name: icon_name) %>
8
+ <% end %>
5
9
  <% else %>
6
10
  <%= render Maglev::Uikit::IconButtonComponent.new(icon_name: icon_name, data: { action: "click->uikit-dropdown#toggle", 'uikit-dropdown-target': 'button' }) %>
7
11
  <% end %>
8
12
  <% end %>
9
13
 
10
- <div class="flex flex-col w-48 text-gray-800">
14
+ <div class="<%= list_item_classes %>">
11
15
  <% items.each do |item| %>
12
16
  <%= item %>
13
17
  <% end %>
@@ -3,30 +3,267 @@
3
3
  module Maglev
4
4
  module Uikit
5
5
  class MenuDropdownComponent < Maglev::Uikit::BaseComponent
6
+ module RenderPolymorphicItems
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ renders_many :items, types: {
11
+ button: lambda { |options = nil|
12
+ Maglev::Uikit::MenuDropdownComponent::ButtonItemComponent.new(options, parent: self)
13
+ },
14
+ link_to: lambda { |options = nil, html_options = nil|
15
+ Maglev::Uikit::MenuDropdownComponent::LinkToItemComponent.new(options, html_options, parent: self)
16
+ },
17
+ button_to: lambda { |options = nil, html_options = nil|
18
+ Maglev::Uikit::MenuDropdownComponent::ButtonToItemComponent.new(options, html_options, parent: self)
19
+ },
20
+ text: lambda { |options = nil|
21
+ Maglev::Uikit::MenuDropdownComponent::TextItemComponent.new(options, parent: self)
22
+ },
23
+ nested_menu: lambda { |placement:|
24
+ Maglev::Uikit::MenuDropdownComponent::NestedMenuComponent.new(placement: placement, parent: self)
25
+ },
26
+ wrapper: lambda { |options = nil|
27
+ Maglev::Uikit::MenuDropdownComponent::WrapperItemComponent.new(options, parent: self)
28
+ },
29
+ divider: -> { Maglev::Uikit::MenuDropdownComponent::DividerItemComponent.new(parent: self) }
30
+ }
31
+ end
32
+ end
33
+
6
34
  renders_one :trigger
7
- renders_many :items
35
+ include RenderPolymorphicItems
8
36
 
9
- attr_reader :icon_name, :placement
37
+ attr_reader :icon_name, :placement, :wrapper_classes, :trigger_classes
10
38
 
11
- def initialize(icon_name: nil, placement: 'bottom-start')
39
+ def initialize(icon_name: nil, placement: 'bottom-start', wrapper_classes: nil, trigger_classes: nil)
12
40
  @icon_name = icon_name
13
41
  @placement = placement
42
+ @wrapper_classes = wrapper_classes
43
+ @trigger_classes = trigger_classes
44
+ end
45
+
46
+ def list_item_classes
47
+ 'text-gray-800 grid grid-cols-[auto_1fr_auto] min-w-[12rem] my-1'
14
48
  end
15
49
 
50
+ # rubocop:disable Metrics/MethodLength
16
51
  def item_classes(...)
17
52
  class_variants(
18
53
  base: %(
19
- flex items-center px-4 py-4 hover:bg-gray-100 w-full
20
- transition-colors duration-200 focus:outline-none cursor-pointer flex-1
21
- )
54
+ col-span-3 grid grid-cols-subgrid
55
+ flex flex-1 items-center px-2 py-3 w-full mx-1 rounded-sm text-left
56
+ transition-colors duration-200 focus:outline-none cursor-pointer
57
+ ),
58
+ variants: {
59
+ danger: 'text-red-600 hover:bg-red-100',
60
+ '!danger': 'hover:bg-gray-100',
61
+ subdued: 'text-gray-400'
62
+ },
63
+ defaults: { danger: false, subdued: false }
22
64
  ).render(...)
23
65
  end
66
+ # rubocop:enable Metrics/MethodLength
24
67
 
25
68
  def form_item_classes(...)
26
69
  class_variants(
27
- base: 'flex items-center focus:outline-none cursor-pointer'
70
+ base: %w[
71
+ col-span-3 grid grid-cols-subgrid
72
+ flex items-center focus:outline-none cursor-pointer
73
+ ]
28
74
  ).render(...)
29
75
  end
76
+
77
+ class ItemComponent < ViewComponent::Base
78
+ renders_one :icon
79
+ renders_one :label
80
+ renders_one :sub_label
81
+ renders_one :right_icon
82
+
83
+ def self.inner_content
84
+ <<-ERB
85
+ <%= render Maglev::Uikit::IconComponent.new(name: icon.to_s, size: '1.15rem', class_names: 'mr-2 shrink-0') if icon? %>
86
+ <span class="<%= 'col-start-2' unless icon? %> whitespace-nowrap whitespace-nowrap truncate overflow-hidden">
87
+ <%= label %>
88
+ </span>
89
+ <%= render Maglev::Uikit::IconComponent.new(name: 'arrow_right', size: '1.15rem', class_names: 'ml-2 shrink-0') if right_arrow? %>
90
+ <span class="col-start-2 whitespace-nowrap whitespace-nowrap truncate overflow-hidden text-xs text-gray-500">
91
+ <%= sub_label %>
92
+ </span>
93
+ ERB
94
+ end
95
+
96
+ def right_arrow?
97
+ false
98
+ end
99
+ end
100
+
101
+ class TextItemComponent < ItemComponent
102
+ attr_reader :options, :parent_component, :variant
103
+
104
+ def initialize(options = nil, parent: nil)
105
+ @parent_component = parent
106
+ @options = options || {}
107
+ @variant = @options.delete(:variant)
108
+
109
+ apply_parent_classes
110
+ end
111
+
112
+ erb_template <<-ERB
113
+ <%= tag.span(**options) do %>
114
+ #{inner_content}
115
+ <% end %>
116
+ ERB
117
+
118
+ private
119
+
120
+ def apply_parent_classes
121
+ options[:class] = parent_component.item_classes(subdued: variant == 'subdued', class: options[:class])
122
+ end
123
+ end
124
+
125
+ class LinkToItemComponent < ItemComponent
126
+ attr_reader :options, :html_options, :parent_component, :variant
127
+
128
+ def initialize(options = nil, html_options = nil, parent: nil)
129
+ @parent_component = parent
130
+ @options = options
131
+ @html_options = html_options || {}
132
+ @variant = @html_options.delete(:variant)
133
+
134
+ apply_parent_classes
135
+ end
136
+
137
+ erb_template <<-ERB
138
+ <%= link_to options, html_options do %>
139
+ #{inner_content}
140
+ <% end %>
141
+ ERB
142
+
143
+ private
144
+
145
+ def apply_parent_classes
146
+ html_options[:class] = parent_component.item_classes(danger: variant == 'danger', class: html_options[:class])
147
+ end
148
+ end
149
+
150
+ class ButtonItemComponent < ItemComponent
151
+ attr_reader :options, :parent_component
152
+
153
+ def initialize(options = nil, parent: nil)
154
+ @parent_component = parent
155
+ @options = (options || {}).merge(type: 'button')
156
+
157
+ apply_parent_classes
158
+ end
159
+
160
+ erb_template <<-ERB
161
+ <%= button_tag options do %>
162
+ #{inner_content}
163
+ <% end %>
164
+ ERB
165
+
166
+ private
167
+
168
+ def apply_parent_classes
169
+ options[:class] = parent_component.item_classes(class: options[:class])
170
+ end
171
+ end
172
+
173
+ class ButtonToItemComponent < LinkToItemComponent
174
+ erb_template <<-ERB
175
+ <%= button_to options, html_options do %>
176
+ #{inner_content}
177
+ <% end %>
178
+ ERB
179
+
180
+ private
181
+
182
+ def apply_parent_classes
183
+ super
184
+ html_options[:form_class] = parent_component.form_item_classes(class: html_options[:form_class])
185
+ end
186
+ end
187
+
188
+ class NestedMenuComponent < ItemComponent
189
+ include RenderPolymorphicItems
190
+
191
+ attr_reader :placement, :parent_component
192
+
193
+ delegate :list_item_classes, :item_classes, :form_item_classes, to: :parent_component
194
+
195
+ alias wrapper_classes form_item_classes
196
+
197
+ def initialize(placement:, parent:)
198
+ @placement = placement
199
+ @parent_component = parent
200
+ end
201
+
202
+ erb_template <<-ERB
203
+ <%= render Maglev::Uikit::DropdownComponent.new(placement: placement, wrapper_classes: wrapper_classes) do |dropdown| %>
204
+ <% dropdown.with_trigger do %>
205
+ <%= button_tag class: item_classes, data: { action: 'click->uikit-dropdown#toggle', 'uikit-dropdown-target': 'button' } do %>
206
+ #{inner_content}#{' '}
207
+ <% end %>
208
+ <% end %>
209
+
210
+ <div class="<%= list_item_classes %>">
211
+ <% items.each do |item| %>
212
+ <%= item %>
213
+ <% end %>
214
+ </div>
215
+ <% end %>
216
+ ERB
217
+
218
+ def right_arrow?
219
+ true
220
+ end
221
+ end
222
+
223
+ class WrapperItemComponent < Maglev::Uikit::BaseComponent
224
+ include RenderPolymorphicItems
225
+
226
+ attr_reader :options, :parent_component
227
+
228
+ delegate :item_classes, :form_item_classes, to: :parent_component
229
+
230
+ def initialize(options, parent: nil)
231
+ @parent_component = parent
232
+ @options = options || {}
233
+
234
+ apply_parent_classes
235
+ end
236
+
237
+ erb_template <<-ERB
238
+ <%= tag.div(**options) do %>
239
+ <% items.each do |item| %>
240
+ <%= item %>
241
+ <% end %>
242
+ <% end %>
243
+ ERB
244
+
245
+ private
246
+
247
+ def apply_parent_classes
248
+ options[:class] = parent_component.form_item_classes(class: options[:class])
249
+ end
250
+ end
251
+
252
+ class DividerItemComponent < ItemComponent
253
+ attr_reader :parent_component
254
+
255
+ def initialize(parent:)
256
+ @parent_component = parent
257
+ end
258
+
259
+ def item_classes(...)
260
+ parent_component.form_item_classes(class: 'border-t border-gray-200 my-1 px-0!')
261
+ end
262
+
263
+ erb_template <<-ERB
264
+ <%= tag.hr class: item_classes %>
265
+ ERB
266
+ end
30
267
  end
31
268
  end
32
269
  end