uchi 0.1.2 → 0.1.4

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 (276) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +222 -0
  3. data/app/assets/config/uchi_manifest.js +2 -0
  4. data/app/assets/javascripts/uchi/application.js +6095 -0
  5. data/app/assets/javascripts/uchi.js +4 -0
  6. data/app/assets/stylesheets/uchi/application.css +3971 -0
  7. data/app/assets/stylesheets/uchi/uchi.css +17 -0
  8. data/app/assets/tailwind/uchi.css +21 -0
  9. data/app/components/flowbite/breadcrumb.rb +33 -0
  10. data/app/components/flowbite/breadcrumb_home.rb +26 -0
  11. data/app/components/flowbite/breadcrumb_item/current.rb +33 -0
  12. data/app/components/flowbite/breadcrumb_item/first.rb +35 -0
  13. data/app/components/flowbite/breadcrumb_item.rb +48 -0
  14. data/app/components/flowbite/breadcrumb_separator.rb +30 -0
  15. data/app/components/flowbite/button/outline.rb +22 -0
  16. data/app/components/flowbite/button/pill.rb +40 -0
  17. data/app/components/flowbite/button.rb +92 -0
  18. data/app/components/flowbite/card.rb +45 -0
  19. data/app/components/flowbite/input/checkbox.rb +73 -0
  20. data/app/components/flowbite/input/date.rb +11 -0
  21. data/app/components/flowbite/input/date_time.rb +11 -0
  22. data/app/components/flowbite/input/email.rb +12 -0
  23. data/app/components/flowbite/input/field.rb +117 -0
  24. data/app/components/flowbite/input/file.rb +30 -0
  25. data/app/components/flowbite/input/hint.rb +57 -0
  26. data/app/components/flowbite/input/label.rb +82 -0
  27. data/app/components/flowbite/input/number.rb +11 -0
  28. data/app/components/flowbite/input/password.rb +11 -0
  29. data/app/components/flowbite/input/phone.rb +11 -0
  30. data/app/components/flowbite/input/radio_button.rb +50 -0
  31. data/app/components/flowbite/input/select.rb +49 -0
  32. data/app/components/flowbite/input/textarea.rb +42 -0
  33. data/app/components/flowbite/input/url.rb +12 -0
  34. data/app/components/flowbite/input/validation_error.rb +11 -0
  35. data/app/components/flowbite/input_field/checkbox.html.erb +14 -0
  36. data/app/components/flowbite/input_field/checkbox.rb +54 -0
  37. data/app/components/flowbite/input_field/date.rb +13 -0
  38. data/app/components/flowbite/input_field/date_time.rb +13 -0
  39. data/app/components/flowbite/input_field/email.rb +13 -0
  40. data/app/components/flowbite/input_field/file.rb +13 -0
  41. data/app/components/flowbite/input_field/input_field.html.erb +8 -0
  42. data/app/components/flowbite/input_field/number.rb +13 -0
  43. data/app/components/flowbite/input_field/password.rb +13 -0
  44. data/app/components/flowbite/input_field/phone.rb +13 -0
  45. data/app/components/flowbite/input_field/radio_button.html.erb +14 -0
  46. data/app/components/flowbite/input_field/radio_button.rb +86 -0
  47. data/app/components/flowbite/input_field/select.rb +31 -0
  48. data/app/components/flowbite/input_field/text.rb +8 -0
  49. data/app/components/flowbite/input_field/textarea.rb +13 -0
  50. data/app/components/flowbite/input_field/url.rb +13 -0
  51. data/app/components/flowbite/input_field.rb +192 -0
  52. data/app/components/flowbite/link.rb +21 -0
  53. data/app/components/flowbite/style.rb +13 -0
  54. data/app/components/flowbite/toast/icon.html.erb +5 -0
  55. data/app/components/flowbite/toast/icon.rb +57 -0
  56. data/app/components/flowbite/toast/toast.html.erb +11 -0
  57. data/app/components/flowbite/toast.rb +34 -0
  58. data/app/components/uchi/field/base.rb +57 -0
  59. data/app/components/uchi/field/belongs_to/edit.html.erb +1 -0
  60. data/app/components/uchi/field/belongs_to/index.html.erb +1 -0
  61. data/app/components/uchi/field/belongs_to/show.html.erb +3 -0
  62. data/app/components/uchi/field/belongs_to.rb +105 -0
  63. data/app/components/uchi/field/blank/edit.html.erb +1 -0
  64. data/app/components/uchi/field/blank/index.html.erb +1 -0
  65. data/app/components/uchi/field/blank/show.html.erb +1 -0
  66. data/app/components/uchi/field/blank.rb +16 -0
  67. data/app/components/uchi/field/boolean/edit.html.erb +1 -0
  68. data/app/components/uchi/field/boolean/index.html.erb +9 -0
  69. data/app/components/uchi/field/boolean/show.html.erb +9 -0
  70. data/app/components/uchi/field/boolean.rb +27 -0
  71. data/app/components/uchi/field/date/edit.html.erb +1 -0
  72. data/app/components/uchi/field/date/index.html.erb +1 -0
  73. data/app/components/uchi/field/date/show.html.erb +1 -0
  74. data/app/components/uchi/field/date.rb +27 -0
  75. data/app/components/uchi/field/date_time/edit.html.erb +1 -0
  76. data/app/components/uchi/field/date_time/index.html.erb +1 -0
  77. data/app/components/uchi/field/date_time/show.html.erb +1 -0
  78. data/app/components/uchi/field/date_time.rb +27 -0
  79. data/app/components/uchi/field/file/edit.html.erb +1 -0
  80. data/app/components/uchi/field/file/index.html.erb +6 -0
  81. data/app/components/uchi/field/file/show.html.erb +8 -0
  82. data/app/components/uchi/field/file.rb +37 -0
  83. data/app/components/uchi/field/has_and_belongs_to_many/edit.html.erb +9 -0
  84. data/app/components/uchi/field/has_and_belongs_to_many/index.html.erb +1 -0
  85. data/app/components/uchi/field/has_and_belongs_to_many/show.html.erb +28 -0
  86. data/app/components/uchi/field/has_and_belongs_to_many.rb +131 -0
  87. data/app/components/uchi/field/has_many/edit.html.erb +1 -0
  88. data/app/components/uchi/field/has_many/index.html.erb +1 -0
  89. data/app/components/uchi/field/has_many/show.html.erb +28 -0
  90. data/app/components/uchi/field/has_many.rb +107 -0
  91. data/app/components/uchi/field/id/index.html.erb +4 -0
  92. data/app/components/uchi/field/id/show.html.erb +4 -0
  93. data/app/components/uchi/field/id.rb +26 -0
  94. data/app/components/uchi/field/image/edit.html.erb +1 -0
  95. data/app/components/uchi/field/image/index.html.erb +6 -0
  96. data/app/components/uchi/field/image/show.html.erb +6 -0
  97. data/app/components/uchi/field/image.rb +38 -0
  98. data/app/components/uchi/field/number/edit.html.erb +1 -0
  99. data/app/components/uchi/field/number/index.html.erb +1 -0
  100. data/app/components/uchi/field/number/show.html.erb +1 -0
  101. data/app/components/uchi/field/number.rb +32 -0
  102. data/app/components/uchi/field/string/edit.html.erb +1 -0
  103. data/app/components/uchi/field/string/index.html.erb +1 -0
  104. data/app/components/uchi/field/string/show.html.erb +1 -0
  105. data/app/components/uchi/field/string.rb +33 -0
  106. data/app/components/uchi/field/text/edit.html.erb +1 -0
  107. data/app/components/uchi/field/text/index.html.erb +1 -0
  108. data/app/components/uchi/field/text/show.html.erb +1 -0
  109. data/app/components/uchi/field/text.rb +38 -0
  110. data/app/components/uchi/ui/breadcrumb/breadcrumb.html.erb +13 -0
  111. data/app/components/uchi/ui/breadcrumb.rb +14 -0
  112. data/app/components/uchi/ui/form/footer/footer.html.erb +5 -0
  113. data/app/components/uchi/ui/form/footer.rb +15 -0
  114. data/app/components/uchi/ui/form/input/collection_checkboxes.html.erb +32 -0
  115. data/app/components/uchi/ui/form/input/collection_checkboxes.rb +125 -0
  116. data/app/components/uchi/ui/frame/frame.html.erb +3 -0
  117. data/app/components/uchi/ui/frame.rb +10 -0
  118. data/app/components/uchi/ui/index/records_table/records_table.html.erb +67 -0
  119. data/app/components/uchi/ui/index/records_table/search_form/search_form.html.erb +21 -0
  120. data/app/components/uchi/ui/index/records_table/search_form.rb +49 -0
  121. data/app/components/uchi/ui/index/records_table.rb +29 -0
  122. data/app/components/uchi/ui/index/turbo_frame.rb +50 -0
  123. data/app/components/uchi/ui/page_header/page_header.html.erb +24 -0
  124. data/app/components/uchi/ui/page_header.rb +18 -0
  125. data/app/components/uchi/ui/pagination/current_link.html.erb +3 -0
  126. data/app/components/uchi/ui/pagination/current_link.rb +10 -0
  127. data/app/components/uchi/ui/pagination/gap.html.erb +3 -0
  128. data/app/components/uchi/ui/pagination/gap.rb +10 -0
  129. data/app/components/uchi/ui/pagination/item.rb +24 -0
  130. data/app/components/uchi/ui/pagination/link.html.erb +3 -0
  131. data/app/components/uchi/ui/pagination/link.rb +10 -0
  132. data/app/components/uchi/ui/pagination/next_link.html.erb +8 -0
  133. data/app/components/uchi/ui/pagination/next_link.rb +22 -0
  134. data/app/components/uchi/ui/pagination/pagination.html.erb +15 -0
  135. data/app/components/uchi/ui/pagination/previous_link.html.erb +8 -0
  136. data/app/components/uchi/ui/pagination/previous_link.rb +22 -0
  137. data/app/components/uchi/ui/pagination.rb +48 -0
  138. data/app/components/uchi/ui/show/attribute_fields/attribute_fields.html.erb +14 -0
  139. data/app/components/uchi/ui/show/attribute_fields.rb +18 -0
  140. data/app/components/uchi/ui/spinner/spinner.html.erb +7 -0
  141. data/app/components/uchi/ui/spinner.rb +15 -0
  142. data/app/controllers/uchi/application_controller.rb +4 -0
  143. data/app/controllers/uchi/controller.rb +13 -0
  144. data/app/controllers/uchi/repository_controller.rb +166 -0
  145. data/app/helpers/uchi/application_helper.rb +17 -0
  146. data/app/jobs/uchi/application_job.rb +4 -0
  147. data/app/mailers/uchi/application_mailer.rb +6 -0
  148. data/app/views/layouts/uchi/_flash_messages.html.erb +10 -0
  149. data/app/views/layouts/uchi/application.html.erb +33 -0
  150. data/app/views/uchi/repository/edit.html.erb +40 -0
  151. data/app/views/uchi/repository/index.html.erb +49 -0
  152. data/app/views/uchi/repository/new.html.erb +37 -0
  153. data/app/views/uchi/repository/show.html.erb +41 -0
  154. data/lib/generators/uchi/controller/controller_generator.rb +16 -0
  155. data/lib/generators/uchi/controller/templates/controller.rb.tt +11 -0
  156. data/lib/generators/uchi/install/install_generator.rb +13 -0
  157. data/lib/generators/uchi/repository/repository_generator.rb +16 -0
  158. data/lib/generators/uchi/repository/templates/repository.rb.tt +11 -0
  159. data/lib/tasks/uchi_tasks.rake +4 -0
  160. data/lib/uchi/application_record.rb +5 -0
  161. data/lib/uchi/engine.rb +24 -0
  162. data/lib/uchi/field/configuration.rb +142 -0
  163. data/lib/uchi/field.rb +80 -0
  164. data/lib/uchi/i18n.rb +13 -0
  165. data/lib/uchi/pagination/controller.rb +26 -0
  166. data/lib/uchi/pagination/page.rb +20 -0
  167. data/lib/uchi/pagy/LICENSE.txt +21 -0
  168. data/lib/uchi/pagy/classes/exceptions.rb +35 -0
  169. data/lib/uchi/pagy/classes/offset/offset.rb +56 -0
  170. data/lib/uchi/pagy/classes/request.rb +38 -0
  171. data/lib/uchi/pagy/modules/abilities/configurable.rb +38 -0
  172. data/lib/uchi/pagy/modules/abilities/linkable.rb +62 -0
  173. data/lib/uchi/pagy/modules/abilities/rangeable.rb +17 -0
  174. data/lib/uchi/pagy/modules/abilities/shiftable.rb +14 -0
  175. data/lib/uchi/pagy/modules/console.rb +40 -0
  176. data/lib/uchi/pagy/toolbox/helpers/loader.rb +19 -0
  177. data/lib/uchi/pagy/toolbox/helpers/page_url.rb +25 -0
  178. data/lib/uchi/pagy/toolbox/paginators/method.rb +21 -0
  179. data/lib/uchi/pagy/toolbox/paginators/offset.rb +35 -0
  180. data/lib/uchi/pagy.rb +60 -0
  181. data/lib/uchi/repository/routes.rb +62 -0
  182. data/lib/uchi/repository/translate.rb +284 -0
  183. data/lib/uchi/repository.rb +156 -0
  184. data/lib/uchi/sort_order.rb +35 -0
  185. data/lib/uchi/version.rb +5 -0
  186. data/lib/uchi.rb +18 -0
  187. data/uchi.gemspec +35 -0
  188. metadata +197 -107
  189. data/.github/dependabot.yml +0 -17
  190. data/.github/workflows/build.yml +0 -23
  191. data/.github/workflows/lint.yml +0 -30
  192. data/package.json +0 -31
  193. data/sig/uchi.rbs +0 -4
  194. data/test/components/uchi/field/belongs_to_test.rb +0 -134
  195. data/test/components/uchi/field/blank_test.rb +0 -119
  196. data/test/components/uchi/field/boolean_test.rb +0 -163
  197. data/test/components/uchi/field/date_test.rb +0 -163
  198. data/test/components/uchi/field/date_time_test.rb +0 -152
  199. data/test/components/uchi/field/has_many_test.rb +0 -138
  200. data/test/components/uchi/field/id_test.rb +0 -113
  201. data/test/components/uchi/field/number_test.rb +0 -163
  202. data/test/components/uchi/field/string_test.rb +0 -159
  203. data/test/controllers/uchi/authors_controller_test.rb +0 -119
  204. data/test/controllers/uchi/repository_controller_test.rb +0 -93
  205. data/test/controllers/uchi/scoped_repository_controller_test.rb +0 -73
  206. data/test/dummy/Rakefile +0 -6
  207. data/test/dummy/app/assets/images/.keep +0 -0
  208. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  209. data/test/dummy/app/controllers/application_controller.rb +0 -4
  210. data/test/dummy/app/controllers/concerns/.keep +0 -0
  211. data/test/dummy/app/controllers/uchi/authors_controller.rb +0 -7
  212. data/test/dummy/app/controllers/uchi/books_controller.rb +0 -7
  213. data/test/dummy/app/controllers/uchi/titles_controller.rb +0 -7
  214. data/test/dummy/app/helpers/application_helper.rb +0 -2
  215. data/test/dummy/app/jobs/application_job.rb +0 -7
  216. data/test/dummy/app/mailers/application_mailer.rb +0 -4
  217. data/test/dummy/app/models/application_record.rb +0 -3
  218. data/test/dummy/app/models/author.rb +0 -3
  219. data/test/dummy/app/models/book.rb +0 -3
  220. data/test/dummy/app/models/concerns/.keep +0 -0
  221. data/test/dummy/app/models/title.rb +0 -3
  222. data/test/dummy/app/uchi/repositories/author.rb +0 -20
  223. data/test/dummy/app/uchi/repositories/book.rb +0 -16
  224. data/test/dummy/app/uchi/repositories/title.rb +0 -17
  225. data/test/dummy/app/views/layouts/application.html.erb +0 -27
  226. data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
  227. data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
  228. data/test/dummy/app/views/pwa/manifest.json.erb +0 -22
  229. data/test/dummy/app/views/pwa/service-worker.js +0 -26
  230. data/test/dummy/bin/dev +0 -2
  231. data/test/dummy/bin/rails +0 -4
  232. data/test/dummy/bin/rake +0 -4
  233. data/test/dummy/bin/setup +0 -34
  234. data/test/dummy/config/application.rb +0 -29
  235. data/test/dummy/config/boot.rb +0 -5
  236. data/test/dummy/config/cable.yml +0 -10
  237. data/test/dummy/config/database.yml +0 -32
  238. data/test/dummy/config/environment.rb +0 -5
  239. data/test/dummy/config/environments/development.rb +0 -69
  240. data/test/dummy/config/environments/production.rb +0 -89
  241. data/test/dummy/config/environments/test.rb +0 -53
  242. data/test/dummy/config/initializers/content_security_policy.rb +0 -25
  243. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -8
  244. data/test/dummy/config/initializers/inflections.rb +0 -16
  245. data/test/dummy/config/locales/da.yml +0 -51
  246. data/test/dummy/config/locales/en.yml +0 -31
  247. data/test/dummy/config/puma.rb +0 -38
  248. data/test/dummy/config/routes.rb +0 -9
  249. data/test/dummy/config/storage.yml +0 -34
  250. data/test/dummy/config.ru +0 -6
  251. data/test/dummy/db/migrate/20251002183635_create_authors.rb +0 -11
  252. data/test/dummy/db/migrate/20251005131726_create_books.rb +0 -9
  253. data/test/dummy/db/migrate/20251005131811_create_titles.rb +0 -11
  254. data/test/dummy/db/schema.rb +0 -38
  255. data/test/dummy/log/.keep +0 -0
  256. data/test/dummy/public/400.html +0 -114
  257. data/test/dummy/public/404.html +0 -114
  258. data/test/dummy/public/406-unsupported-browser.html +0 -114
  259. data/test/dummy/public/422.html +0 -114
  260. data/test/dummy/public/500.html +0 -114
  261. data/test/dummy/public/icon.png +0 -0
  262. data/test/dummy/public/icon.svg +0 -3
  263. data/test/dummy/storage/.keep +0 -0
  264. data/test/dummy/test/fixtures/authors.yml +0 -11
  265. data/test/dummy/test/models/author_test.rb +0 -7
  266. data/test/dummy/tmp/.keep +0 -0
  267. data/test/dummy/tmp/pids/.keep +0 -0
  268. data/test/dummy/tmp/storage/.keep +0 -0
  269. data/test/test_helper.rb +0 -15
  270. data/test/uchi/field_test.rb +0 -63
  271. data/test/uchi/i18n_test.rb +0 -18
  272. data/test/uchi/repository/routes_test.rb +0 -49
  273. data/test/uchi/repository/translate_test.rb +0 -263
  274. data/test/uchi/repository_test.rb +0 -137
  275. data/test/uchi/sort_order_test.rb +0 -47
  276. data/test/uchi_test.rb +0 -7
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Field
5
+ class String < Field
6
+ class Edit < Uchi::Field::Base::Edit
7
+ private
8
+
9
+ def options
10
+ options = {
11
+ attribute: field.name,
12
+ form: form,
13
+ label: {content: label}
14
+ }
15
+ options[:hint] = {content: hint} if hint.present?
16
+ options
17
+ end
18
+ end
19
+
20
+ class Index < Uchi::Field::Base::Index
21
+ end
22
+
23
+ class Show < Uchi::Field::Base::Show
24
+ end
25
+
26
+ protected
27
+
28
+ def default_searchable?
29
+ true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1 @@
1
+ <%= render(Flowbite::InputField::Textarea.new(**options)) %>
@@ -0,0 +1 @@
1
+ <%= field.value(record) %>
@@ -0,0 +1 @@
1
+ <%= field.value(record) %>
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Field
5
+ class Text < Field
6
+ class Edit < Uchi::Field::Base::Edit
7
+ private
8
+
9
+ def options
10
+ options = {
11
+ attribute: field.name,
12
+ form: form,
13
+ label: {content: label},
14
+ input: {options: {rows: 8}}
15
+ }
16
+ options[:hint] = {content: hint} if hint.present?
17
+ options
18
+ end
19
+ end
20
+
21
+ class Index < Uchi::Field::Base::Index
22
+ end
23
+
24
+ class Show < Uchi::Field::Base::Show
25
+ end
26
+
27
+ protected
28
+
29
+ def default_on
30
+ [:edit, :show]
31
+ end
32
+
33
+ def default_searchable?
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ <%= render(Flowbite::Breadcrumb.new) do |breadcrumb| %>
2
+ <% items.each.with_index do |item, index| %>
3
+ <% breadcrumb.with_item do %>
4
+ <% if index.zero? %>
5
+ <%= render Flowbite::BreadcrumbItem::First.new(href: item[:href]).with_content(item[:label]) %>
6
+ <% elsif index == items.size - 1 %>
7
+ <%= render Flowbite::BreadcrumbItem::Current.new.with_content(item[:label]) %>
8
+ <% else %>
9
+ <%= render Flowbite::BreadcrumbItem.new(href: item[:href]).with_content(item[:label]) %>
10
+ <% end %>
11
+ <% end %>
12
+ <% end %>
13
+ <% end %>
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ class Breadcrumb < ViewComponent::Base
6
+ attr_reader :items
7
+
8
+ def initialize(items:)
9
+ super()
10
+ @items = items
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ <div class="flex items-center space-x-4">
2
+ <% actions.each do |action| %>
3
+ <%= action %>
4
+ <% end %>
5
+ </div>
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ module Form
6
+ class Footer < ViewComponent::Base
7
+ renders_many :actions
8
+
9
+ def render?
10
+ actions.any?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ <div>
2
+ <% if render_label? %>
3
+ <%= form.label attribute, label_text, class: label_classes.join(" ") %>
4
+ <% end %>
5
+
6
+ <div class="mt-2 space-y-3">
7
+ <%= form.collection_check_boxes(
8
+ attribute,
9
+ collection,
10
+ value_method,
11
+ text_method,
12
+ collection_check_boxes_options
13
+ ) do |b| %>
14
+ <div class="<%= checkbox_item_classes.join(" ") %>">
15
+ <div class="flex items-center h-5">
16
+ <%= b.check_box(class: checkbox_classes.join(" "), disabled: disabled?) %>
17
+ </div>
18
+ <div class="ms-2 text-sm">
19
+ <%= b.label(class: checkbox_label_classes.join(" ")) %>
20
+ </div>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+
25
+ <% if render_hint? %>
26
+ <p class="<%= hint_classes.join(" ") %>"><%= hint_text %></p>
27
+ <% end %>
28
+
29
+ <% errors.each do |error| %>
30
+ <%= render(Flowbite::Input::ValidationError.new) { error.upcase_first } %>
31
+ <% end %>
32
+ </div>
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ module Form
6
+ module Input
7
+ # A component that wraps Rails' collection_check_boxes form helper
8
+ # to render multiple checkboxes from a collection using Flowbite styling.
9
+ #
10
+ # Example usage:
11
+ # <%= render Uchi::Ui::Form::Input::CollectionCheckboxes.new(
12
+ # attribute: :tag_ids,
13
+ # collection: @tags,
14
+ # form: form,
15
+ # label: "Select Tags",
16
+ # value_method: :id,
17
+ # text_method: :name
18
+ # ) %>
19
+ class CollectionCheckboxes < ViewComponent::Base
20
+ attr_reader :attribute, :collection, :form, :label_text, :hint_text,
21
+ :value_method, :text_method, :disabled
22
+
23
+ def initialize(
24
+ attribute:,
25
+ collection:,
26
+ form:,
27
+ label: nil,
28
+ hint: nil,
29
+ value_method: :id,
30
+ text_method: :name,
31
+ disabled: false,
32
+ options: {}
33
+ )
34
+ super()
35
+ @attribute = attribute
36
+ @collection = collection
37
+ @form = form
38
+ @label_text = label
39
+ @hint_text = hint
40
+ @value_method = value_method
41
+ @text_method = text_method
42
+ @disabled = disabled
43
+ @options = options || {}
44
+ end
45
+
46
+ def object
47
+ @object ||= form.object
48
+ end
49
+
50
+ def errors
51
+ @errors ||= object.errors[attribute] || []
52
+ end
53
+
54
+ def errors?
55
+ errors.any?
56
+ end
57
+
58
+ def disabled?
59
+ !!disabled
60
+ end
61
+
62
+ def render_label?
63
+ label_text.present?
64
+ end
65
+
66
+ def render_hint?
67
+ hint_text.present?
68
+ end
69
+
70
+ def label_classes
71
+ base = ["block", "mb-2", "text-sm", "font-medium"]
72
+ if disabled?
73
+ base + ["text-gray-400", "dark:text-gray-500"]
74
+ elsif errors?
75
+ base + ["text-red-700", "dark:text-red-500"]
76
+ else
77
+ base + ["text-gray-900", "dark:text-white"]
78
+ end
79
+ end
80
+
81
+ def hint_classes
82
+ base = ["text-xs", "font-normal", "mt-1"]
83
+ if disabled?
84
+ base + ["text-gray-400", "dark:text-gray-500"]
85
+ else
86
+ base + ["text-gray-500", "dark:text-gray-300"]
87
+ end
88
+ end
89
+
90
+ def checkbox_item_classes
91
+ ["flex", "items-start", "mb-3"]
92
+ end
93
+
94
+ def checkbox_classes
95
+ base = ["w-4", "h-4", "rounded-sm", "focus:ring-2", "focus:ring-offset-2"]
96
+ if disabled?
97
+ base + ["text-blue-600", "bg-gray-100", "border-gray-300",
98
+ "dark:bg-gray-700", "dark:border-gray-600"]
99
+ elsif errors?
100
+ base + ["text-red-600", "bg-red-50", "border-red-500",
101
+ "focus:ring-red-500", "dark:focus:ring-red-600",
102
+ "dark:bg-gray-700", "dark:border-red-500"]
103
+ else
104
+ base + ["text-blue-600", "bg-gray-100", "border-gray-300",
105
+ "focus:ring-blue-500", "dark:focus:ring-blue-600",
106
+ "dark:bg-gray-700", "dark:border-gray-600"]
107
+ end
108
+ end
109
+
110
+ def checkbox_label_classes
111
+ if disabled?
112
+ ["ms-2", "text-sm", "font-medium", "text-gray-400", "dark:text-gray-500"]
113
+ else
114
+ ["ms-2", "text-sm", "font-medium", "text-gray-900", "dark:text-gray-300"]
115
+ end
116
+ end
117
+
118
+ def collection_check_boxes_options
119
+ @options
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,3 @@
1
+ <div class="bg-white border border-gray-200 shadow-sm md:rounded-lg dark:bg-gray-800 dark:border-gray-700">
2
+ <%= content %>
3
+ </div>
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ # Renders a styled frame around its content. Rounded corners, border,
6
+ # shadow, no inner whitespace.
7
+ class Frame < ViewComponent::Base
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ <table class="w-full text-left text-gray-500 rtl:text-right dark:text-gray-400">
2
+ <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
3
+ <tr>
4
+ <% columns.each do |field| %>
5
+ <% component_class = field.index_component_class %>
6
+ <th scope="col" class="px-4 py-3 md:px-6 whitespace-nowrap <%= component_class.classes_for_table_cell.join(" ") %>">
7
+ <% if field.sortable? %>
8
+ <% if field.name == sort_order&.name %>
9
+ <% if sort_order&.ascending? %>
10
+ <%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :desc})) do %>
11
+ <%= repository.translate.field_label(field) %>
12
+ <svg class="inline w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
13
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19V5m0 14-4-4m4 4 4-4"/>
14
+ </svg>
15
+ <% end %>
16
+ <% else %>
17
+ <%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :asc})) do %>
18
+ <%= repository.translate.field_label(field) %>
19
+ <svg class="inline w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
20
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v13m0-13 4 4m-4-4-4 4"/>
21
+ </svg>
22
+ <% end %>
23
+ <% end %>
24
+ <% else %>
25
+ <%= link_to(repository.routes.path_for(:index, query: query, scope: scope, sort: {:by => field.name, :direction => :asc})) do %>
26
+ <%= repository.translate.field_label(field) %>
27
+ <svg class="inline w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
28
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 20V7m0 13-4-4m4 4 4-4m4-12v13m0-13 4 4m-4-4-4 4"/>
29
+ </svg>
30
+ <% end %>
31
+ <% end %>
32
+ <% else %>
33
+ <%= repository.translate.field_label(field) %>
34
+ <% end %>
35
+ </th>
36
+ <% end %>
37
+ <th scope="col" class="px-4 py-3 md:px-6"></th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ <% records.each do |record| %>
42
+ <tr class="bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
43
+ <% columns.each do |field| %>
44
+ <% component_class = field.index_component_class %>
45
+ <td class="px-4 py-4 md:px-6 <%= component_class.classes_for_table_cell.join(" ") %>">
46
+ <%= render(field.index_component(record: record, repository: repository)) %>
47
+ </td>
48
+ <% end %>
49
+ <td class="px-4 py-4 text-right md:px-6">
50
+ <div class="flex justify-end space-x-2">
51
+ <%= link_to(repository.routes.path_for(:show, id: record.id), class: "inline-block hover:text-blue-600 hover:dark:text-blue-500", :data => {:"turbo-frame" => "_top"}) do %>
52
+ <svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
53
+ <path stroke="currentColor" stroke-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
54
+ <path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
55
+ </svg>
56
+ <% end %>
57
+ <%= link_to(path_for_edit(record), class: "inline-block hover:text-blue-600 hover:dark:text-blue-500", :data => {:"turbo-frame" => "_top"}) do %>
58
+ <svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
59
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
60
+ </svg>
61
+ <% end %>
62
+ </div>
63
+ </td>
64
+ </tr>
65
+ <% end %>
66
+ </tbody>
67
+ </table>
@@ -0,0 +1,21 @@
1
+ <%= form_tag(repository.routes.path_for(:index), class: "flex items-center max-w-sm", method: :get) do %>
2
+ <%= label_tag(:query, repository.translate.search_label, class: "sr-only") %>
3
+ <div class="relative w-full">
4
+ <%= search_field_tag(:query, query, class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500") %>
5
+ </div>
6
+ <%= button_tag(class: "p-2.5 ms-2 text-sm font-medium text-white bg-blue-700 rounded-lg border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800") do %>
7
+ <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
8
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
9
+ </svg>
10
+ <span class="sr-only"><%= repository.translate.search_button %></span>
11
+ <% end %>
12
+
13
+ <% if scoped? %>
14
+ <%= hidden_field_tag("scope[field]", scope[:field]) %>
15
+ <%= hidden_field_tag("scope[id]", scope[:id]) %>
16
+ <%= hidden_field_tag("scope[inverse_of]", scope[:inverse_of]) %>
17
+ <%= hidden_field_tag("scope[model]", scope[:model]) %>
18
+ <% end %>
19
+ <%= hidden_field_tag("sort[by]", sort_by) if sort_by? %>
20
+ <%= hidden_field_tag("sort[direction]", sort_direction) if sort_direction? %>
21
+ <% end %>
@@ -0,0 +1,49 @@
1
+ module Uchi
2
+ module Ui
3
+ module Index
4
+ class RecordsTable
5
+ # Renders a search for the records table.
6
+ #
7
+ # Based on Flowbites Simple search input
8
+ # (https://flowbite.com/docs/forms/search-input/#simple-search-input)
9
+ class SearchForm < ViewComponent::Base
10
+ attr_reader :params, :repository
11
+
12
+ def initialize(params:, repository:)
13
+ super()
14
+ @params = params
15
+ @repository = repository
16
+ end
17
+
18
+ def query
19
+ params[:query]
20
+ end
21
+
22
+ def scope
23
+ params[:scope]
24
+ end
25
+
26
+ def scoped?
27
+ scope.present?
28
+ end
29
+
30
+ def sort_by
31
+ params.dig(:sort, :by)
32
+ end
33
+
34
+ def sort_by?
35
+ sort_by.present?
36
+ end
37
+
38
+ def sort_direction
39
+ params.dig(:sort, :direction)
40
+ end
41
+
42
+ def sort_direction?
43
+ sort_direction.present?
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ module Index
6
+ class RecordsTable < ViewComponent::Base
7
+ # Returns the columns to be displayed in this table. Each column is a
8
+ # representation of a Field from repository. Defaults to all fields.
9
+ attr_reader :columns
10
+
11
+ attr_reader :query, :sort_order, :records, :repository, :scope
12
+
13
+ def initialize(columns:, records:, repository:, query: nil, scope: nil, sort_order: nil)
14
+ super()
15
+ @columns = columns
16
+ @query = query
17
+ @sort_order = sort_order
18
+ @records = records
19
+ @repository = repository
20
+ @scope = scope
21
+ end
22
+
23
+ def path_for_edit(record)
24
+ repository.routes.path_for(:edit, id: record.id, scope: scope)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ module Index
6
+ class TurboFrame < ViewComponent::Base
7
+ attr_reader :repository, :scope, :src
8
+
9
+ def call
10
+ helpers
11
+ .turbo_frame_tag(turbo_frame_id, **options) {
12
+ content
13
+ }
14
+ end
15
+
16
+ def initialize(repository:, scope: nil, src: nil)
17
+ super()
18
+ @repository = repository
19
+ @scope = scope
20
+ @src = src
21
+ end
22
+
23
+ protected
24
+
25
+ def options
26
+ options = {}
27
+ options[:src] = src if src.present?
28
+ options
29
+ end
30
+
31
+ def scoped?
32
+ scope.present?
33
+ end
34
+
35
+ def turbo_frame_id
36
+ parts = if scoped?
37
+ [
38
+ scope[:model],
39
+ scope[:id],
40
+ scope[:field]
41
+ ]
42
+ else
43
+ [repository.controller_name]
44
+ end
45
+ parts.compact.join("_")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ <header class="px-4 mb-6 space-y-6 md:px-0">
2
+ <div>
3
+ <% if breadcrumb? %>
4
+ <%= breadcrumb %>
5
+ <% end %>
6
+ </div>
7
+
8
+ <div class="items-center justify-between space-x-6 space-y-3 md:flex md:px-0">
9
+ <div>
10
+ <h1 class="mb-1 text-3xl font-semibold tracking-tight text-gray-900 dark:text-white group"><%= title %></h1>
11
+ <% if description.present? %>
12
+ <div class="text-lg text-gray-500 lg:mb-0 dark:text-gray-400 lg:max-w-2xl"><%= description %></div>
13
+ <% end %>
14
+ </div>
15
+
16
+ <% if actions.any? %>
17
+ <div class="flex items-center space-x-2">
18
+ <% actions.each do |action| %>
19
+ <%= action %>
20
+ <% end %>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+ </header>
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ class PageHeader < ViewComponent::Base
6
+ attr_reader :description, :title
7
+
8
+ renders_many :actions
9
+ renders_one :breadcrumb, Uchi::Ui::Breadcrumb
10
+
11
+ def initialize(title:, description: nil)
12
+ super()
13
+ @description = description
14
+ @title = title
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to(@paginator.page, page_url(page_number), aria: { current: "page" }, class: "z-10 flex items-center justify-center px-3 h-8 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white") %>
3
+ </li>
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ class Pagination
6
+ class CurrentLink < Item
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ <li class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400">
2
+ &hellip;
3
+ </li>
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ class Pagination
6
+ class Gap < Item
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ class Pagination
6
+ class Item < ViewComponent::Base
7
+ attr_reader :page_number, :paginator
8
+
9
+ # @param paginator [Uchi::Pagination::Page] The page object
10
+ def initialize(paginator:, page_number: nil)
11
+ super()
12
+ @page_number = page_number
13
+ @paginator = paginator
14
+ end
15
+
16
+ # Returns the URL for a given page. page can be a number, of :first,
17
+ # :last, :previous, :next, :current.
18
+ def page_url(page)
19
+ paginator.page_url(page)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ <li>
2
+ <%= link_to(page_number, page_url(page_number), class: "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") %>
3
+ </li>
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ module Ui
5
+ class Pagination
6
+ class Link < Item
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ <li>
2
+ <%= link_to(url, class: "flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white") do %>
3
+ <span class="sr-only">Next</span>
4
+ <svg class="w-3 h-3 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
5
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
6
+ </svg>
7
+ <% end %>
8
+ </li>