solidus_admin 0.0.2 → 0.2.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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.json +7 -0
  3. data/README.md +2 -2
  4. data/Rakefile +26 -0
  5. data/app/assets/builds/.keep +0 -0
  6. data/app/assets/builds/solidus_admin/tailwind.css +2799 -0
  7. data/app/assets/config/solidus_admin_manifest.js +1 -0
  8. data/app/assets/stylesheets/solidus_admin/application.tailwind.css +4 -0
  9. data/app/assets/stylesheets/solidus_admin/dark.css +12 -0
  10. data/app/assets/stylesheets/solidus_admin/dimmed.css +11 -0
  11. data/app/components/solidus_admin/adjustment_reasons/index/component.rb +43 -0
  12. data/app/components/solidus_admin/base_component.rb +29 -1
  13. data/app/components/solidus_admin/layout/feedback/component.html.erb +15 -0
  14. data/app/components/solidus_admin/layout/feedback/component.rb +4 -0
  15. data/app/components/solidus_admin/layout/feedback/component.yml +3 -0
  16. data/app/components/solidus_admin/layout/navigation/account/component.html.erb +74 -0
  17. data/app/components/solidus_admin/layout/navigation/account/component.js +16 -0
  18. data/app/components/solidus_admin/layout/navigation/account/component.rb +36 -0
  19. data/app/components/solidus_admin/{sidebar → layout/navigation}/component.html.erb +9 -9
  20. data/app/components/solidus_admin/layout/navigation/component.rb +26 -0
  21. data/app/components/solidus_admin/{sidebar → layout/navigation}/item/component.html.erb +5 -5
  22. data/app/components/solidus_admin/{sidebar → layout/navigation}/item/component.rb +2 -2
  23. data/app/components/solidus_admin/layout/page_helpers.rb +55 -0
  24. data/app/components/solidus_admin/{skip_link → layout/skip_link}/component.rb +2 -4
  25. data/app/components/solidus_admin/option_types/index/component.rb +71 -0
  26. data/app/components/solidus_admin/orders/cart/component.html.erb +77 -0
  27. data/app/components/solidus_admin/orders/cart/component.js +37 -0
  28. data/app/components/solidus_admin/orders/cart/component.rb +7 -0
  29. data/app/components/solidus_admin/orders/cart/component.yml +3 -0
  30. data/app/components/solidus_admin/orders/cart/result/component.html.erb +26 -0
  31. data/app/components/solidus_admin/orders/cart/result/component.rb +11 -0
  32. data/app/components/solidus_admin/orders/index/component.rb +92 -23
  33. data/app/components/solidus_admin/orders/index/component.yml +10 -4
  34. data/app/components/solidus_admin/orders/show/address/component.html.erb +67 -0
  35. data/app/components/solidus_admin/orders/show/address/component.js +9 -0
  36. data/app/components/solidus_admin/orders/show/address/component.rb +53 -0
  37. data/app/components/solidus_admin/orders/show/address/component.yml +14 -0
  38. data/app/components/solidus_admin/orders/show/component.html.erb +76 -0
  39. data/app/components/solidus_admin/orders/show/component.js +7 -0
  40. data/app/components/solidus_admin/orders/show/component.rb +40 -0
  41. data/app/components/solidus_admin/orders/show/component.yml +21 -0
  42. data/app/components/solidus_admin/orders/show/customer_search/component.html.erb +14 -0
  43. data/app/components/solidus_admin/orders/show/customer_search/component.js +14 -0
  44. data/app/components/solidus_admin/orders/show/customer_search/component.rb +7 -0
  45. data/app/components/solidus_admin/orders/show/customer_search/component.yml +2 -0
  46. data/app/components/solidus_admin/orders/show/customer_search/result/component.html.erb +17 -0
  47. data/app/components/solidus_admin/orders/show/customer_search/result/component.rb +11 -0
  48. data/app/components/solidus_admin/orders/show/email/component.html.erb +18 -0
  49. data/app/components/solidus_admin/orders/show/email/component.rb +15 -0
  50. data/app/components/solidus_admin/orders/show/email/component.yml +8 -0
  51. data/app/components/solidus_admin/orders/show/summary/component.html.erb +14 -0
  52. data/app/components/solidus_admin/orders/show/summary/component.rb +7 -0
  53. data/app/components/solidus_admin/orders/show/summary/component.yml +8 -0
  54. data/app/components/solidus_admin/payment_methods/index/component.rb +103 -0
  55. data/app/components/solidus_admin/payment_methods/index/component.yml +10 -0
  56. data/app/components/solidus_admin/products/index/component.rb +48 -41
  57. data/app/components/solidus_admin/products/index/component.yml +8 -7
  58. data/app/components/solidus_admin/products/show/component.html.erb +32 -38
  59. data/app/components/solidus_admin/products/show/component.rb +2 -0
  60. data/app/components/solidus_admin/products/show/component.yml +5 -5
  61. data/app/components/solidus_admin/products/status/component.rb +20 -18
  62. data/app/components/solidus_admin/products/status/component.yml +1 -0
  63. data/app/components/solidus_admin/products/stock/component.rb +38 -0
  64. data/app/components/solidus_admin/products/stock/component.yml +5 -0
  65. data/app/components/solidus_admin/promotion_categories/index/component.rb +56 -0
  66. data/app/components/solidus_admin/promotions/index/component.rb +104 -0
  67. data/app/components/solidus_admin/promotions/index/component.yml +10 -0
  68. data/app/components/solidus_admin/properties/index/component.rb +64 -0
  69. data/app/components/solidus_admin/refund_reasons/index/component.rb +53 -0
  70. data/app/components/solidus_admin/refunds_and_returns/component.rb +40 -0
  71. data/app/components/solidus_admin/refunds_and_returns/component.yml +3 -0
  72. data/app/components/solidus_admin/reimbursement_types/index/component.rb +27 -0
  73. data/app/components/solidus_admin/return_reasons/index/component.rb +42 -0
  74. data/app/components/solidus_admin/shipping/component.rb +30 -0
  75. data/app/components/solidus_admin/shipping/component.yml +3 -0
  76. data/app/components/solidus_admin/shipping_categories/index/component.rb +46 -0
  77. data/app/components/solidus_admin/shipping_methods/index/component.rb +61 -0
  78. data/app/components/solidus_admin/stock_items/edit/component.html.erb +89 -0
  79. data/app/components/solidus_admin/stock_items/edit/component.js +17 -0
  80. data/app/components/solidus_admin/stock_items/edit/component.rb +23 -0
  81. data/app/components/solidus_admin/stock_items/edit/component.yml +10 -0
  82. data/app/components/solidus_admin/stock_items/index/component.rb +174 -0
  83. data/app/components/solidus_admin/stock_items/index/component.yml +10 -0
  84. data/app/components/solidus_admin/stock_locations/index/component.rb +89 -0
  85. data/app/components/solidus_admin/stock_locations/index/component.yml +3 -0
  86. data/app/components/solidus_admin/store_credit_reasons/index/component.rb +52 -0
  87. data/app/components/solidus_admin/stores/index/component.rb +58 -0
  88. data/app/components/solidus_admin/tax_categories/index/component.rb +58 -0
  89. data/app/components/solidus_admin/tax_rates/index/component.rb +88 -0
  90. data/app/components/solidus_admin/taxes/component.rb +25 -0
  91. data/app/components/solidus_admin/taxes/component.yml +3 -0
  92. data/app/components/solidus_admin/taxonomies/index/component.rb +53 -0
  93. data/app/components/solidus_admin/ui/badge/component.rb +18 -8
  94. data/app/components/solidus_admin/ui/badge/component.yml +3 -0
  95. data/app/components/solidus_admin/ui/button/component.rb +32 -32
  96. data/app/components/solidus_admin/ui/details_list/component.html.erb +10 -0
  97. data/app/components/solidus_admin/ui/details_list/component.rb +7 -0
  98. data/app/components/solidus_admin/ui/dropdown/component.html.erb +50 -0
  99. data/app/components/solidus_admin/ui/dropdown/component.js +16 -0
  100. data/app/components/solidus_admin/ui/dropdown/component.rb +28 -0
  101. data/app/components/solidus_admin/ui/dropdown/component.yml +2 -0
  102. data/app/components/solidus_admin/ui/forms/address/component.html.erb +36 -0
  103. data/app/components/solidus_admin/ui/forms/address/component.js +34 -0
  104. data/app/components/solidus_admin/ui/forms/address/component.rb +14 -0
  105. data/app/components/solidus_admin/ui/forms/field/component.html.erb +5 -4
  106. data/app/components/solidus_admin/ui/forms/field/component.rb +53 -15
  107. data/app/components/solidus_admin/ui/forms/input/component.rb +11 -5
  108. data/app/components/solidus_admin/ui/forms/search/component.html.erb +52 -0
  109. data/app/components/solidus_admin/ui/forms/search/component.js +116 -0
  110. data/app/components/solidus_admin/ui/forms/search/component.rb +8 -0
  111. data/app/components/solidus_admin/ui/forms/search/component.yml +4 -0
  112. data/app/components/solidus_admin/ui/forms/search/result/component.rb +12 -0
  113. data/app/components/solidus_admin/ui/forms/search_field/component.html.erb +20 -0
  114. data/app/{javascript/solidus_admin/controllers/hello_controller.js → components/solidus_admin/ui/forms/search_field/component.js} +4 -2
  115. data/app/components/solidus_admin/ui/forms/search_field/component.rb +10 -0
  116. data/app/components/solidus_admin/ui/forms/search_field/component.yml +2 -0
  117. data/app/components/solidus_admin/ui/forms/switch/component.rb +27 -31
  118. data/app/components/solidus_admin/ui/forms/switch_field/component.html.erb +20 -0
  119. data/app/components/solidus_admin/ui/forms/switch_field/component.rb +11 -0
  120. data/app/components/solidus_admin/ui/icon/component.rb +4 -0
  121. data/app/components/solidus_admin/ui/modal/component.html.erb +37 -0
  122. data/app/components/solidus_admin/ui/modal/component.rb +12 -0
  123. data/app/components/solidus_admin/ui/modal/component.yml +2 -0
  124. data/app/components/solidus_admin/ui/pages/index/component.html.erb +30 -0
  125. data/app/components/solidus_admin/ui/pages/index/component.rb +119 -0
  126. data/app/components/solidus_admin/ui/pages/index/component.yml +4 -0
  127. data/app/components/solidus_admin/ui/panel/component.html.erb +26 -12
  128. data/app/components/solidus_admin/ui/panel/component.rb +17 -0
  129. data/app/components/solidus_admin/ui/panel/component.yml +1 -3
  130. data/app/components/solidus_admin/ui/resource_item/component.html.erb +10 -0
  131. data/app/components/solidus_admin/ui/resource_item/component.rb +9 -0
  132. data/app/components/solidus_admin/ui/tab/component.rb +9 -8
  133. data/app/components/solidus_admin/ui/table/component.html.erb +147 -135
  134. data/app/components/solidus_admin/ui/table/component.js +56 -17
  135. data/app/components/solidus_admin/ui/table/component.rb +94 -70
  136. data/app/components/solidus_admin/ui/table/component.yml +0 -1
  137. data/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb +72 -0
  138. data/app/components/solidus_admin/ui/table/ransack_filter/component.js +73 -0
  139. data/app/components/solidus_admin/ui/table/ransack_filter/component.rb +68 -0
  140. data/app/components/solidus_admin/ui/table/ransack_filter/component.yml +3 -0
  141. data/app/components/solidus_admin/ui/table/toolbar/component.rb +21 -0
  142. data/app/components/solidus_admin/ui/thumbnail/component.rb +46 -0
  143. data/app/components/solidus_admin/ui/toast/component.html.erb +9 -5
  144. data/app/components/solidus_admin/ui/toast/component.js +9 -6
  145. data/app/components/solidus_admin/ui/toast/component.rb +2 -2
  146. data/app/components/solidus_admin/ui/toggletip/component.html.erb +14 -10
  147. data/app/components/solidus_admin/ui/toggletip/component.js +22 -4
  148. data/app/components/solidus_admin/ui/toggletip/component.rb +8 -85
  149. data/app/components/solidus_admin/users/index/component.rb +96 -0
  150. data/app/components/solidus_admin/users/index/component.yml +15 -0
  151. data/app/components/solidus_admin/zones/index/component.rb +63 -0
  152. data/app/controllers/solidus_admin/addresses_controller.rb +92 -0
  153. data/app/controllers/solidus_admin/adjustment_reasons_controller.rb +40 -0
  154. data/app/controllers/solidus_admin/base_controller.rb +1 -0
  155. data/app/controllers/solidus_admin/controller_helpers/authorization.rb +5 -1
  156. data/app/controllers/solidus_admin/controller_helpers/locale.rb +2 -2
  157. data/app/controllers/solidus_admin/controller_helpers/search.rb +48 -0
  158. data/app/controllers/solidus_admin/controller_helpers/theme.rb +30 -0
  159. data/app/controllers/solidus_admin/countries_controller.rb +12 -0
  160. data/app/controllers/solidus_admin/customers_controller.rb +29 -0
  161. data/app/controllers/solidus_admin/line_items_controller.rb +45 -0
  162. data/app/controllers/solidus_admin/option_types_controller.rb +46 -0
  163. data/app/controllers/solidus_admin/orders_controller.rb +104 -7
  164. data/app/controllers/solidus_admin/payment_methods_controller.rb +52 -0
  165. data/app/controllers/solidus_admin/products_controller.rb +26 -17
  166. data/app/controllers/solidus_admin/promotion_categories_controller.rb +29 -0
  167. data/app/controllers/solidus_admin/promotions_controller.rb +46 -0
  168. data/app/controllers/solidus_admin/properties_controller.rb +33 -0
  169. data/app/controllers/solidus_admin/refund_reasons_controller.rb +40 -0
  170. data/app/controllers/solidus_admin/reimbursement_types_controller.rb +31 -0
  171. data/app/controllers/solidus_admin/return_reasons_controller.rb +40 -0
  172. data/app/controllers/solidus_admin/shipping_categories_controller.rb +40 -0
  173. data/app/controllers/solidus_admin/shipping_methods_controller.rb +40 -0
  174. data/app/controllers/solidus_admin/stock_items_controller.rb +67 -0
  175. data/app/controllers/solidus_admin/stock_locations_controller.rb +40 -0
  176. data/app/controllers/solidus_admin/store_credit_reasons_controller.rb +40 -0
  177. data/app/controllers/solidus_admin/stores_controller.rb +40 -0
  178. data/app/controllers/solidus_admin/tax_categories_controller.rb +40 -0
  179. data/app/controllers/solidus_admin/tax_rates_controller.rb +40 -0
  180. data/app/controllers/solidus_admin/taxonomies_controller.rb +46 -0
  181. data/app/controllers/solidus_admin/users_controller.rb +50 -0
  182. data/app/controllers/solidus_admin/zones_controller.rb +40 -0
  183. data/app/javascript/solidus_admin/controllers/components.js +3 -1
  184. data/app/javascript/solidus_admin/controllers/confirm_controller.js +21 -0
  185. data/app/javascript/solidus_admin/controllers/details_click_outside_controller.js +12 -0
  186. data/app/javascript/solidus_admin/controllers/readonly_when_submitting_controller.js +17 -0
  187. data/app/javascript/solidus_admin/controllers/sortable_controller.js +33 -0
  188. data/app/views/layouts/solidus_admin/application.html.erb +16 -10
  189. data/app/views/layouts/solidus_admin/preview.html.erb +2 -1
  190. data/app/views/solidus_admin/base/unauthorized.html.erb +4 -0
  191. data/config/importmap.rb +2 -0
  192. data/config/initializers/view_component.rb +20 -0
  193. data/config/locales/adjustment_reasons.en.yml +6 -0
  194. data/config/locales/customers.en.yml +7 -0
  195. data/config/locales/errors.en.yml +7 -0
  196. data/config/locales/line_items.en.yml +9 -0
  197. data/config/locales/{main_nav.en.yml → menu_item.en.yml} +8 -2
  198. data/config/locales/option_types.en.yml +6 -0
  199. data/config/locales/orders.en.yml +9 -0
  200. data/config/locales/payment_methods.en.yml +6 -0
  201. data/config/locales/promotion_categories.en.yml +6 -0
  202. data/config/locales/promotions.en.yml +6 -0
  203. data/config/locales/properties.en.yml +6 -0
  204. data/config/locales/refund_reasons_.en.yml +6 -0
  205. data/config/locales/reimbursement_types.en.yml +4 -0
  206. data/config/locales/return_reasons.en.yml +6 -0
  207. data/config/locales/shipping_categories.en.yml +6 -0
  208. data/config/locales/shipping_methods.en.yml +6 -0
  209. data/config/locales/stock_items.en.yml +4 -0
  210. data/config/locales/stock_locations.en.yml +6 -0
  211. data/config/locales/store_credit_reasons.en.yml +6 -0
  212. data/config/locales/stores.en.yml +6 -0
  213. data/config/locales/tax_categories.en.yml +6 -0
  214. data/config/locales/tax_rates.en.yml +6 -0
  215. data/config/locales/taxonomies.en.yml +6 -0
  216. data/config/locales/users.en.yml +6 -0
  217. data/config/locales/zones.en.yml +6 -0
  218. data/config/routes.rb +50 -3
  219. data/config/tailwind.config.js +119 -0
  220. data/docs/{customizing_main_navigation.md → customizing_menu_items.md} +2 -2
  221. data/docs/customizing_tailwindcss.md +57 -0
  222. data/docs/customizing_view_components.md +17 -38
  223. data/lib/generators/solidus_admin/install/install_generator.rb +13 -4
  224. data/lib/generators/solidus_admin/install/templates/config/initializers/{solidus_admin.rb → solidus_admin.rb.tt} +10 -14
  225. data/lib/solidus_admin/admin_resources.rb +23 -0
  226. data/lib/solidus_admin/configuration.rb +95 -67
  227. data/lib/solidus_admin/install_tailwindcss.rb +102 -0
  228. data/lib/solidus_admin/{main_nav_item.rb → menu_item.rb} +2 -2
  229. data/lib/solidus_admin/version.rb +1 -1
  230. data/lib/solidus_admin.rb +1 -2
  231. data/lib/tasks/tailwind.rake +10 -0
  232. data/solidus_admin.gemspec +3 -4
  233. metadata +193 -50
  234. data/app/assets/stylesheets/solidus_admin/application.tailwind.css.erb +0 -35
  235. data/app/components/solidus_admin/feedback/component.html.erb +0 -11
  236. data/app/components/solidus_admin/feedback/component.rb +0 -4
  237. data/app/components/solidus_admin/feedback/component.yml +0 -5
  238. data/app/components/solidus_admin/orders/index/component.html.erb +0 -31
  239. data/app/components/solidus_admin/products/index/component.html.erb +0 -30
  240. data/app/components/solidus_admin/sidebar/account_nav/component.html.erb +0 -67
  241. data/app/components/solidus_admin/sidebar/account_nav/component.rb +0 -15
  242. data/app/components/solidus_admin/sidebar/component.rb +0 -21
  243. data/app/components/solidus_admin/ui/panel/component.js +0 -14
  244. data/config/solidus_admin/tailwind.config.js.erb +0 -95
  245. data/docs/customizing_tailwind.md +0 -78
  246. data/lib/solidus_admin/tailwindcss.rb +0 -58
  247. data/lib/tasks/tailwindcss.rake +0 -55
  248. /data/app/components/solidus_admin/{sidebar/account_nav → layout/navigation/account}/component.yml +0 -0
  249. /data/app/components/solidus_admin/{sidebar → layout/navigation}/component.js +0 -0
  250. /data/app/components/solidus_admin/{sidebar → layout/navigation}/component.yml +0 -0
  251. /data/app/components/solidus_admin/{skip_link → layout/skip_link}/component.yml +0 -0
@@ -3,168 +3,180 @@
3
3
  rounded-lg
4
4
  border
5
5
  border-gray-100
6
- overflow-hidden
6
+ <%= 'overflow-hidden' unless @search %>
7
7
  "
8
8
  data-controller="<%= stimulus_id %>"
9
9
  data-<%= stimulus_id %>-selected-row-class="bg-gray-15"
10
+ data-<%= stimulus_id %>-mode-value="<%= initial_mode %>"
11
+ data-<%= stimulus_id %>-sortable-value="<%= should_enable_sortable? %>"
12
+ data-action="
13
+ <%= component("ui/table/ransack_filter").stimulus_id %>:search-><%= stimulus_id %>#search
14
+ <%= component("ui/table/ransack_filter").stimulus_id %>:showSearch-><%= stimulus_id %>#showSearch
15
+ "
10
16
  >
11
- <% toolbar_classes = "h-14 p-2 bg-white border-b border-gray-100 justify-start items-center gap-2 visible:flex hidden:hidden" %>
17
+ <% if @search %>
18
+ <div role="search">
19
+ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "searchToolbar", hidden: initial_mode != "search") do %>
20
+ <%= form_with(
21
+ url: @search.url,
22
+ method: :get,
23
+ html: {
24
+ id: search_form_id,
25
+ class: 'flex-grow',
26
+ "data-turbo-action": "replace",
27
+ "data-#{stimulus_id}-target": "searchForm",
28
+ "data-action": "input->#{stimulus_id}#search change->#{stimulus_id}#search",
29
+ },
30
+ ) do |form| %>
31
+ <%= hidden_field_tag @search.scope_param_name, @search.current_scope.name if @search.scopes.present? %>
32
+ <%= render component('ui/forms/search_field').new(
33
+ name: @search.searchbar_param_name,
34
+ value: @search.value[@search.searchbar_key],
35
+ placeholder: t('.search_placeholder', resources: @data.plural_name),
36
+ "aria-label": t('.search_placeholder', resources: @data.plural_name),
37
+ "data-#{stimulus_id}-target": "searchField",
38
+ "data-turbo-permanent": "true",
39
+ id: "#{stimulus_id}-search-field-#{@id}",
40
+ ) %>
41
+ <% end %>
12
42
 
13
- <div role="search">
14
- <div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="searchToolbar">
15
- <%= form_with(
16
- url: @search_url,
17
- method: :get,
18
- html: {
19
- id: search_form_id,
20
- class: 'flex-grow',
21
- "data-turbo-frame": table_frame_id,
22
- "data-turbo-action": "replace",
23
- "data-#{stimulus_id}-target": "searchForm",
24
- "data-action": "reset->#{stimulus_id}#search",
25
- },
26
- ) do |form| %>
27
- <label class="items-center gap-1 p-0 inline-flex w-full justify-start relative">
28
- <%= render component("ui/icon").new(name: 'search-line', class: "w-[1.4em] h-[1.4em] fill-gray-500 absolute ml-3") %>
29
- <input
30
- name="q[<%= @search_key %>]"
31
- value="<%= params.dig(:q, @search_key) %>"
32
- type="search"
33
- placeholder="<%= t('.search_placeholder', resources: resource_plural_name) %>"
34
- class="peer w-full placeholder:text-gray-400 py-1.5 px-10 bg-white rounded border border-gray-300 search-cancel:appearance-none"
35
- data-<%= stimulus_id %>-target="searchField"
36
- data-action="<%= stimulus_id %>#search"
37
- aria-label="<%= t('.search_placeholder', resources: resource_plural_name) %>"
38
- >
39
- <button
40
- class="absolute right-0 mr-3 peer-placeholder-shown:hidden"
41
- data-action="<%= stimulus_id %>#clearSearch"
42
- aria-label="<%= t('.clear') %>"
43
- >
44
- <%= render component("ui/icon").new(name: 'close-circle-fill', class: "w-[1.4em] h-[1.4em] fill-gray-500") %>
45
- </button>
46
- </label>
43
+ <% if @search.scopes.any? %>
44
+ <div class="ml-4">
45
+ <%= render component("ui/button").new(
46
+ text: t('.cancel'),
47
+ scheme: :ghost,
48
+ "data-action": "#{stimulus_id}#resetSearchAndFilters",
49
+ ) %>
50
+ </div>
51
+ <% end %>
47
52
  <% end %>
48
53
 
49
- <div class="ml-4">
50
- <%= render component("ui/button").new(
51
- text: t('.cancel'),
52
- scheme: :ghost,
53
- "data-action": "#{stimulus_id}#cancelSearch",
54
- ) %>
55
- </div>
56
- </div>
57
-
58
- <% if @filters.any? %>
59
- <div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="filterToolbar">
60
- <div class="font-semibold text-gray-700 text-sm px-2"><%= t('.refine_search') %>:</div>
61
- <% @filters.each do |filter| %>
62
- <label class="flex gap-2 px-2">
63
- <%= render component('ui/forms/checkbox').new(
64
- name: filter[:name],
65
- value: filter[:value],
66
- size: :s,
67
- form: search_form_id,
68
- 'data-action': "#{stimulus_id}#search",
69
- ) %>
70
- <span class="text-gray-700 leading-none text-sm self-center"><%= filter[:label] %></span>
71
- </label>
54
+ <% if @search.filters.any? %>
55
+ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "filterToolbar", hidden: initial_mode != "search", class: "flex-wrap") do %>
56
+ <% @search.filters.each_with_index do |filter, index| %>
57
+ <%= render_ransack_filter_dropdown(filter, index) %>
58
+ <% end %>
72
59
  <% end %>
73
- </div>
74
- <% end %>
60
+ <% end %>
75
61
 
76
- <div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="scopesToolbar">
77
- <div class="flex-grow">
78
- <%= render component("ui/tab").new(text: "All", current: true, href: "") %>
79
- </div>
62
+ <% if @search.scopes.any? %>
63
+ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "scopesToolbar", hidden: initial_mode != "scopes") do %>
64
+ <div class="flex-grow">
65
+ <%= form_with(url: @search.url, method: :get) do %>
66
+ <% @search.scopes.each do |scope| %>
67
+ <%= render component("ui/tab").new(
68
+ tag: :button,
69
+ type: :submit,
70
+ text: scope.label,
71
+ current: scope == @search.current_scope,
72
+ name: @search.scope_param_name,
73
+ value: scope.name,
74
+ ) %>
75
+ <% end %>
76
+ <% end %>
77
+ </div>
80
78
 
81
- <%= render component("ui/button").new(
82
- 'aria-label': t('.filter'),
83
- icon: "filter-3-line",
84
- scheme: :secondary,
85
- "data-action": "#{stimulus_id}#showSearch",
86
- ) %>
79
+ <%= render component("ui/button").new(
80
+ 'aria-label': t('.filter'),
81
+ icon: "filter-3-line",
82
+ scheme: :secondary,
83
+ "data-action": "#{stimulus_id}#showSearch",
84
+ ) %>
85
+ <% end %>
86
+ <% end %>
87
87
  </div>
88
- </div>
88
+ <% end %>
89
89
 
90
- <div class="<%= toolbar_classes %>" data-<%= stimulus_id %>-target="batchToolbar" role="toolbar" aria-label="<%= t(".batch_actions") %>">
90
+ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "batchToolbar", role: "toolbar", "aria-label": t(".batch_actions"), hidden: true) do %>
91
91
  <%= form_tag '', id: batch_actions_form_id %>
92
- <% @batch_actions.each do |batch_action| %>
92
+ <% @data.batch_actions.each do |batch_action| %>
93
93
  <%= render_batch_action_button(batch_action) %>
94
94
  <% end %>
95
- </div>
95
+ <% end %>
96
+
97
+ <table class="table-fixed w-full border-collapse">
98
+ <colgroup>
99
+ <% @data.columns.each do |column| %>
100
+ <col <%= tag.attributes(**column.col) if column.col %>">
101
+ <% end %>
102
+ </colgroup>
96
103
 
97
- <%= turbo_frame_tag table_frame_id, target: "_top" do %>
98
- <table class="table-fixed w-full border-collapse">
99
- <colgroup>
100
- <% @columns.each do |column| %>
101
- <col class="<%= column.class_name %>">
104
+ <thead
105
+ class="bg-gray-15 text-gray-700 text-left text-small"
106
+ data-<%= stimulus_id %>-target="defaultHeader"
107
+ >
108
+ <tr>
109
+ <% @data.columns.each do |column| %>
110
+ <%= render_header_cell(column.header) %>
102
111
  <% end %>
103
- </colgroup>
112
+ </tr>
113
+ </thead>
104
114
 
115
+ <% if @data.batch_actions && @data.rows.any? %>
105
116
  <thead
106
- class="bg-gray-15 text-gray-700 text-left text-small"
107
- data-<%= stimulus_id %>-target="defaultHeader"
117
+ data-<%= stimulus_id %>-target="batchHeader"
118
+ class="bg-white color-black text-xs leading-none text-left"
119
+ hidden
108
120
  >
109
121
  <tr>
110
- <% @columns.each do |column| %>
111
- <%= render_header_cell(column.header) %>
112
- <% end %>
122
+ <%= render_header_cell(selectable_column.header) %>
123
+ <%= render_header_cell(content_tag(:div, safe_join([
124
+ content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"),
125
+ " #{t('.rows_selected')}.",
126
+ ])), colspan: @data.columns.count - 1) %>
113
127
  </tr>
114
128
  </thead>
129
+ <% end %>
115
130
 
116
- <% if @batch_actions %>
117
- <thead
118
- data-<%= stimulus_id %>-target="batchHeader"
119
- class="bg-white color-black text-xs leading-none text-left"
120
- hidden
131
+ <tbody
132
+ class="bg-white text-3.5 line-[150%] text-black"
133
+ data-<%= stimulus_id %>-target="tableBody"
134
+ <%= "data-controller=sortable" if should_enable_sortable? %>
135
+ <%= "data-sortable-param-value=#{@sortable.param}" if @sortable&.param %>
136
+ <%= "data-sortable-handle-value=#{@sortable.handle}" if @sortable&.handle %>
137
+ <%= "data-sortable-animation-value=#{@sortable.animation}" if @sortable&.animation %>
138
+ >
139
+ <% @data.rows.each do |row| %>
140
+ <tr
141
+ class="border-b border-gray-100 last:border-0 hover:bg-gray-50 cursor-pointer <%= 'bg-gray-15 text-gray-700' if @data.fade&.call(row) %>"
142
+ <% if @data.url %>
143
+ data-action="click-><%= stimulus_id %>#rowClicked"
144
+ data-<%= stimulus_id %>-url-param="<%= @data.url.call(row) %>"
145
+ <%= "data-sortable-url=#{@sortable.url.call(row)}" if @sortable&.url %>
146
+ <% end %>
121
147
  >
122
- <tr>
123
- <%= render_header_cell(selectable_column.header) %>
124
- <%= render_header_cell(content_tag(:div, safe_join([
125
- content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"),
126
- " #{t('.rows_selected')}.",
127
- ])), colspan: @columns.count - 1) %>
128
- </div>
129
- </thead>
148
+ <% @data.columns.each do |column| %>
149
+ <%= render_data_cell(column, row) %>
150
+ <% end %>
151
+ </tr>
130
152
  <% end %>
131
153
 
132
- <tbody class="bg-white text-3.5 line-[150%] text-black">
133
- <% @rows.each do |row| %>
134
- <tr class="<%= row_class_for(row) %>">
135
- <% @columns.each do |column| %>
136
- <%= render_data_cell(column.data, row) %>
137
- <% end %>
138
- </tr>
139
- <% end %>
140
-
141
- <% if @rows.empty? && @model_class %>
142
- <tr>
143
- <td
144
- colspan="<%= @columns.size %>"
145
- class="text-center py-4 text-3.5 line-[150%] text-black bg-white"
146
- >
147
- <%= t('.no_resources_found', resources: resource_plural_name) %>
148
- </td>
149
- </tr>
150
- <% end %>
151
- </tbody>
152
-
153
- <% if @prev_page_link || @next_page_link %>
154
- <tfoot>
155
- <tr>
156
- <td colspan="<%= @columns.size %>" class="py-4 bg-white">
157
- <div class="flex justify-center">
158
- <%= render component('ui/table/pagination').new(
159
- prev_link: @prev_page_link,
160
- next_link: @next_page_link
161
- ) %>
162
- </div>
163
- </td>
164
- </tr>
165
- </tfoot>
154
+ <% if @data.rows.empty? && @data.plural_name %>
155
+ <tr>
156
+ <td
157
+ colspan="<%= @data.columns.size %>"
158
+ class="text-center py-4 text-3.5 line-[150%] text-black bg-white rounded-b-lg"
159
+ >
160
+ <%= t('.no_resources_found', resources: @data.plural_name) %>
161
+ </td>
162
+ </tr>
166
163
  <% end %>
164
+ </tbody>
167
165
 
168
- </table>
169
- <% end %>
166
+ <% if @data.prev || @data.next %>
167
+ <tfoot>
168
+ <tr>
169
+ <td colspan="<%= @data.columns.size %>" class="py-4 bg-white rounded-b-lg border-t border-gray-100">
170
+ <div class="flex justify-center">
171
+ <%= render component('ui/table/pagination').new(
172
+ prev_link: @data.prev,
173
+ next_link: @data.next
174
+ ) %>
175
+ </div>
176
+ </td>
177
+ </tr>
178
+ </tfoot>
179
+ <% end %>
180
+
181
+ </table>
170
182
  </div>
@@ -13,12 +13,14 @@ export default class extends Controller {
13
13
  "filterToolbar",
14
14
  "defaultHeader",
15
15
  "batchHeader",
16
+ "tableBody",
16
17
  "selectedRowsCount",
17
18
  ]
18
19
 
19
20
  static classes = ["selectedRow"]
20
21
  static values = {
21
22
  mode: { type: String, default: "scopes" },
23
+ sortable: { type: Boolean, default: false },
22
24
  }
23
25
 
24
26
  initialize() {
@@ -29,16 +31,22 @@ export default class extends Controller {
29
31
  this.search = debounce(this.search.bind(this), 200)
30
32
  }
31
33
 
32
- connect() {
33
- if (this.searchFieldTarget.value !== "") this.modeValue = "search"
34
+ // Determine if sortable should be enabled
35
+ modeValueChanged() {
36
+ const shouldSetSortable = this.sortableValue && this.modeValue !== "batch" && this.modeValue !== "search"
34
37
 
35
- this.render()
38
+ if (shouldSetSortable) {
39
+ this.tableBodyTarget.setAttribute('data-controller', 'sortable')
40
+ } else {
41
+ this.tableBodyTarget.removeAttribute('data-controller')
42
+ }
36
43
  }
37
44
 
38
- showSearch(event) {
45
+ showSearch({ detail: { avoidFocus } }) {
39
46
  this.modeValue = "search"
40
47
  this.render()
41
- this.searchFieldTarget.focus()
48
+
49
+ if (!avoidFocus) this.searchFieldTarget.focus()
42
50
  }
43
51
 
44
52
  search() {
@@ -50,32 +58,38 @@ export default class extends Controller {
50
58
  this.search()
51
59
  }
52
60
 
53
- cancelSearch() {
54
- this.clearSearch()
61
+ resetSearchAndFilters() {
62
+ if (this.hasFilterToolbarTarget) {
63
+ this.filterToolbarTarget.querySelectorAll('fieldset').forEach(fieldset => fieldset.disabled = true)
64
+ }
55
65
 
56
- this.modeValue = "scopes"
57
- this.render()
66
+ this.searchFieldTarget.disabled = true
67
+ this.searchFormTarget.submit()
58
68
  }
59
69
 
60
70
  selectRow(event) {
61
71
  if (this.checkboxTargets.some((checkbox) => checkbox.checked)) {
62
72
  this.modeValue = "batch"
63
- } else if (this.searchFieldTarget.value !== '') {
73
+ } else if (this.hasSearchFieldTarget && (this.searchFieldTarget.value !== '')) {
64
74
  this.modeValue = "search"
65
- } else {
75
+ } else if (this.hasScopesToolbarTarget) {
66
76
  this.modeValue = "scopes"
77
+ } else {
78
+ this.modeValue = "search"
67
79
  }
68
80
 
69
81
  this.render()
70
82
  }
71
83
 
72
84
  selectAllRows(event) {
73
- if (this.modeValue = event.target.checked) {
85
+ if (event.target.checked) {
74
86
  this.modeValue = "batch"
75
- } else if (this.searchFieldTarget.value !== '') {
87
+ } else if (this.hasSearchFieldTarget && (this.searchFieldTarget.value !== '')) {
76
88
  this.modeValue = "search"
77
- } else {
89
+ } else if (this.hasScopesToolbarTarget) {
78
90
  this.modeValue = "scopes"
91
+ } else {
92
+ this.modeValue = "search"
79
93
  }
80
94
 
81
95
  this.checkboxTargets.forEach((checkbox) => (checkbox.checked = event.target.checked))
@@ -83,10 +97,32 @@ export default class extends Controller {
83
97
  this.render()
84
98
  }
85
99
 
100
+ rowClicked(event) {
101
+ // If the user clicked on a link, button, input or summary, skip the row url visit
102
+ if (event.target.closest("td").contains(event.target.closest("a,select,textarea,button,input,summary"))) return
103
+
104
+ if (this.modeValue === "batch") {
105
+ this.toggleCheckbox(event.currentTarget)
106
+ } else {
107
+ window.Turbo.visit(event.params.url)
108
+ }
109
+ }
110
+
111
+ toggleCheckbox(row) {
112
+ const checkbox = this.checkboxTargets.find(selection => row.contains(selection))
113
+
114
+ if (checkbox) {
115
+ checkbox.checked = !checkbox.checked
116
+ this.selectRow()
117
+ }
118
+ }
119
+
86
120
  render() {
87
121
  const selectedRows = this.checkboxTargets.filter((checkbox) => checkbox.checked)
88
122
 
89
- this.searchToolbarTarget.toggleAttribute("hidden", this.modeValue !== "search")
123
+ if (this.hasSearchFieldTarget) {
124
+ this.searchToolbarTarget.toggleAttribute("hidden", this.modeValue !== "search")
125
+ }
90
126
 
91
127
  if (this.hasFilterToolbarTarget) {
92
128
  this.filterToolbarTarget.toggleAttribute("hidden", this.modeValue !== "search")
@@ -96,7 +132,9 @@ export default class extends Controller {
96
132
  this.batchHeaderTarget.toggleAttribute("hidden", this.modeValue !== "batch")
97
133
  this.defaultHeaderTarget.toggleAttribute("hidden", this.modeValue === "batch")
98
134
 
99
- this.scopesToolbarTarget.toggleAttribute("hidden", this.modeValue !== "scopes")
135
+ if (this.hasScopesToolbarTarget) {
136
+ this.scopesToolbarTarget.toggleAttribute("hidden", this.modeValue !== "scopes")
137
+ }
100
138
 
101
139
  // Update the rows background color
102
140
  this.checkboxTargets.filter((checkbox) =>
@@ -111,7 +149,8 @@ export default class extends Controller {
111
149
  checkbox.indeterminate = false
112
150
  checkbox.checked = false
113
151
 
114
- if (selectedRows.length === this.checkboxTargets.length) checkbox.checked = true
152
+ if (this.checkboxTargets.length > 0 && selectedRows.length === this.checkboxTargets.length)
153
+ checkbox.checked = true
115
154
  else if (selectedRows.length > 0) checkbox.indeterminate = true
116
155
  })
117
156
  }
@@ -1,62 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
4
- # @param id [String] A unique identifier for the table component.
5
- # @param model_class [ActiveModel::Translation] The model class used for translations.
6
- # @param rows [Array] The collection of objects that will be passed to columns for display.
7
- # @param fade_row_proc [Proc, nil] A proc determining if a row should have a faded appearance.
8
- # @param search_key [Symbol] The key for searching.
9
- # @param search_url [String] The base URL for searching.
10
- #
11
- # @param columns [Array<Hash>] The array of column definitions.
12
- # @option columns [Symbol|Proc|#to_s] :header The column header.
13
- # @option columns [Symbol|Proc|#to_s] :data The data accessor for the column.
14
- # @option columns [String] :class_name (optional) The class name for the column.
15
- #
16
- # @param batch_actions [Array<Hash>] The array of batch action definitions.
17
- # @option batch_actions [String] :display_name The batch action display name.
18
- # @option batch_actions [String] :icon The batch action icon.
19
- # @option batch_actions [String] :action The batch action path.
20
- # @option batch_actions [String] :method The batch action HTTP method for the provided path.
21
- #
22
- #
23
- # @param filters [Array<Hash>] The array of filter definitions.
24
- # @option filters [String] :name The filter's name.
25
- # @option filters [Any] :value The filter's value.
26
- # @option filters [String] :label The filter's label.
27
- #
28
- # @param prev_page_link [String, nil] The link to the previous page.
29
- # @param next_page_link [String, nil] The link to the next page.
30
- def initialize(
31
- id:,
32
- model_class:,
33
- rows:,
34
- search_key:,
35
- search_url:,
36
- fade_row_proc: nil,
37
- columns: [],
38
- batch_actions: [],
39
- filters: [],
40
- prev_page_link: nil,
41
- next_page_link: nil
42
- )
43
- @columns = columns.map { Column.new(**_1) }
44
- @batch_actions = batch_actions.map { BatchAction.new(**_1) }
45
- @filters = filters.map { Filter.new(**_1) }
46
- @id = id
47
- @model_class = model_class
48
- @rows = rows
49
- @fade_row_proc = fade_row_proc
50
- @search_key = search_key
51
- @search_url = search_url
52
- @prev_page_link = prev_page_link
53
- @next_page_link = next_page_link
54
-
55
- @columns.unshift selectable_column if batch_actions.present?
4
+ BatchAction = Struct.new(:display_name, :icon, :action, :method, keyword_init: true) # rubocop:disable Lint/StructNewOverride
5
+ Column = Struct.new(:header, :data, :col, :wrap, keyword_init: true)
6
+ Filter = Struct.new(:presentation, :combinator, :attribute, :predicate, :options, keyword_init: true)
7
+ Scope = Struct.new(:name, :label, :default, keyword_init: true)
8
+ Sortable = Struct.new(:url, :param, :animation, :handle, keyword_init: true)
9
+ private_constant :BatchAction, :Column, :Filter, :Scope, :Sortable
10
+
11
+ class Data < Struct.new(:rows, :class, :url, :prev, :next, :columns, :fade, :batch_actions, keyword_init: true) # rubocop:disable Lint/StructNewOverride,Style/StructInheritance
12
+ def initialize(**args)
13
+ super
14
+
15
+ self.columns = columns.map do |column|
16
+ column.is_a?(Symbol) ? Column.new(wrap: false, header: column, data: column) : Column.new(wrap: false, **column)
17
+ end
18
+ self.batch_actions = batch_actions.to_a.map { |action| BatchAction.new(**action) }
19
+ end
20
+
21
+ def plural_name
22
+ self[:class].model_name.human.pluralize if self[:class]
23
+ end
56
24
  end
57
25
 
58
- def resource_plural_name
59
- @model_class.model_name.human.pluralize
26
+ class Search < Struct.new(:name, :value, :url, :searchbar_key, :scopes, :filters, keyword_init: true) # rubocop:disable Style/StructInheritance
27
+ def initialize(**args)
28
+ super
29
+
30
+ self.filters = filters.to_a.map { |filter| Filter.new(**filter) }
31
+ self.scopes = scopes.to_a.map { |scope| Scope.new(**scope) }
32
+ end
33
+
34
+ def current_scope
35
+ scopes.find { |scope| scope.name.to_s == value[:scope].presence } || default_scope
36
+ end
37
+
38
+ def default_scope
39
+ scopes.find(&:default)
40
+ end
41
+
42
+ def on_default_scope?
43
+ current_scope == default_scope
44
+ end
45
+
46
+ def scope_param_name
47
+ "#{name}[scope]"
48
+ end
49
+
50
+ def searchbar_param_name
51
+ "#{name}[#{searchbar_key}]"
52
+ end
53
+
54
+ def value
55
+ super || {}
56
+ end
57
+ end
58
+
59
+ def initialize(id:, data:, search: nil, sortable: nil)
60
+ @id = id
61
+ @data = Data.new(**data)
62
+ @data.columns.unshift selectable_column if @data.batch_actions.present? && @data.rows.present?
63
+ @search = Search.new(**search) if search
64
+ @sortable = Sortable.new(**sortable) if sortable
60
65
  end
61
66
 
62
67
  def selectable_column
@@ -79,7 +84,7 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
79
84
  "aria-label": t('.select_row'),
80
85
  )
81
86
  },
82
- class_name: 'w-[52px]',
87
+ col: { class: 'w-[52px]' },
83
88
  )
84
89
  end
85
90
 
@@ -87,10 +92,6 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
87
92
  @batch_actions_form_id ||= "#{stimulus_id}--batch-actions-#{@id}"
88
93
  end
89
94
 
90
- def table_frame_id
91
- @table_frame_id ||= "#{stimulus_id}--table-frame-#{@id}"
92
- end
93
-
94
95
  def search_form_id
95
96
  @search_form_id ||= "#{stimulus_id}--search-form-#{@id}"
96
97
  end
@@ -112,9 +113,22 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
112
113
  )
113
114
  end
114
115
 
116
+ def render_ransack_filter_dropdown(filter, index)
117
+ render component("ui/table/ransack_filter").new(
118
+ presentation: filter.presentation,
119
+ search_param: @search.name,
120
+ combinator: filter.combinator,
121
+ attribute: filter.attribute,
122
+ predicate: filter.predicate,
123
+ options: filter.options,
124
+ form: search_form_id,
125
+ index: index,
126
+ )
127
+ end
128
+
115
129
  def render_header_cell(cell, **attrs)
116
130
  cell = cell.call if cell.respond_to?(:call)
117
- cell = @model_class.human_attribute_name(cell) if cell.is_a?(Symbol)
131
+ cell = @data[:class].human_attribute_name(cell) if cell.is_a?(Symbol)
118
132
  cell = cell.render_in(self) if cell.respond_to?(:render_in)
119
133
 
120
134
  content_tag(:th, cell, class: %{
@@ -128,23 +142,33 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent
128
142
  }, **attrs)
129
143
  end
130
144
 
131
- def render_data_cell(cell, data)
145
+ def render_data_cell(column, data)
146
+ cell = column.data
132
147
  cell = cell.call(data) if cell.respond_to?(:call)
133
148
  cell = data.public_send(cell) if cell.is_a?(Symbol)
134
149
  cell = cell.render_in(self) if cell.respond_to?(:render_in)
150
+ cell = tag.div(cell, class: "flex items-center gap-1.5 justify-start overflow-x-hidden") if column.wrap
135
151
 
136
- content_tag(:td, content_tag(:div, cell, class: "flex items-center gap-1.5"), class: "py-2 px-4 h-10 vertical-align-middle leading-none")
152
+ tag.td(cell, class: "
153
+ py-2 px-4 h-10 vertical-align-middle leading-none
154
+ [tr:last-child_&:first-child]:rounded-bl-lg [tr:last-child_&:last-child]:rounded-br-lg
155
+ ")
137
156
  end
138
157
 
139
- def row_class_for(row)
140
- classes = ['border-b', 'border-gray-100']
141
- classes << ['bg-gray-15', 'text-gray-700'] if @fade_row_proc&.call(row)
158
+ def current_scope_name
159
+ @search.current_scope.name
160
+ end
142
161
 
143
- classes.join(' ')
162
+ def initial_mode
163
+ @initial_mode ||=
164
+ if @search && (@search.value[@search.searchbar_key] || @search.scopes.none?)
165
+ "search"
166
+ else
167
+ "scopes"
168
+ end
144
169
  end
145
170
 
146
- Column = Struct.new(:header, :data, :class_name, keyword_init: true)
147
- BatchAction = Struct.new(:display_name, :icon, :action, :method, keyword_init: true) # rubocop:disable Lint/StructNewOverride
148
- Filter = Struct.new(:name, :value, :label, keyword_init: true)
149
- private_constant :Column, :BatchAction, :Filter
171
+ def should_enable_sortable?
172
+ @sortable && @search&.on_default_scope?
173
+ end
150
174
  end