uchi 0.1.3 → 0.1.5

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 (283) 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 +5 -0
  62. data/app/components/uchi/field/belongs_to.rb +112 -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 +368 -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 +189 -97
  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/CHANGELOG.md +0 -29
  193. data/docs/fields.md +0 -82
  194. data/docs/repositories.md +0 -63
  195. data/package.json +0 -31
  196. data/sig/uchi.rbs +0 -4
  197. data/test/components/uchi/field/belongs_to_test.rb +0 -134
  198. data/test/components/uchi/field/blank_test.rb +0 -119
  199. data/test/components/uchi/field/boolean_test.rb +0 -163
  200. data/test/components/uchi/field/date_test.rb +0 -163
  201. data/test/components/uchi/field/date_time_test.rb +0 -152
  202. data/test/components/uchi/field/has_and_belongs_to_many_test.rb +0 -144
  203. data/test/components/uchi/field/has_many_test.rb +0 -138
  204. data/test/components/uchi/field/id_test.rb +0 -113
  205. data/test/components/uchi/field/number_test.rb +0 -163
  206. data/test/components/uchi/field/string_test.rb +0 -159
  207. data/test/components/uchi/field/text_test.rb +0 -160
  208. data/test/components/uchi/ui/form/input/collection_checkboxes_test.rb +0 -171
  209. data/test/controllers/uchi/authors_controller_test.rb +0 -120
  210. data/test/controllers/uchi/repository_controller_test.rb +0 -93
  211. data/test/controllers/uchi/scoped_repository_controller_test.rb +0 -73
  212. data/test/dummy/Rakefile +0 -6
  213. data/test/dummy/app/assets/images/.keep +0 -0
  214. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  215. data/test/dummy/app/controllers/application_controller.rb +0 -4
  216. data/test/dummy/app/controllers/concerns/.keep +0 -0
  217. data/test/dummy/app/controllers/uchi/authors_controller.rb +0 -7
  218. data/test/dummy/app/controllers/uchi/books_controller.rb +0 -7
  219. data/test/dummy/app/controllers/uchi/titles_controller.rb +0 -7
  220. data/test/dummy/app/helpers/application_helper.rb +0 -2
  221. data/test/dummy/app/jobs/application_job.rb +0 -7
  222. data/test/dummy/app/mailers/application_mailer.rb +0 -4
  223. data/test/dummy/app/models/application_record.rb +0 -3
  224. data/test/dummy/app/models/author.rb +0 -5
  225. data/test/dummy/app/models/book.rb +0 -4
  226. data/test/dummy/app/models/concerns/.keep +0 -0
  227. data/test/dummy/app/models/title.rb +0 -3
  228. data/test/dummy/app/uchi/repositories/author.rb +0 -20
  229. data/test/dummy/app/uchi/repositories/book.rb +0 -16
  230. data/test/dummy/app/uchi/repositories/title.rb +0 -17
  231. data/test/dummy/app/views/layouts/application.html.erb +0 -27
  232. data/test/dummy/app/views/layouts/mailer.html.erb +0 -13
  233. data/test/dummy/app/views/layouts/mailer.text.erb +0 -1
  234. data/test/dummy/app/views/pwa/manifest.json.erb +0 -22
  235. data/test/dummy/app/views/pwa/service-worker.js +0 -26
  236. data/test/dummy/bin/dev +0 -2
  237. data/test/dummy/bin/rails +0 -4
  238. data/test/dummy/bin/rake +0 -4
  239. data/test/dummy/bin/setup +0 -34
  240. data/test/dummy/config/application.rb +0 -29
  241. data/test/dummy/config/boot.rb +0 -5
  242. data/test/dummy/config/cable.yml +0 -10
  243. data/test/dummy/config/database.yml +0 -32
  244. data/test/dummy/config/environment.rb +0 -5
  245. data/test/dummy/config/environments/development.rb +0 -69
  246. data/test/dummy/config/environments/production.rb +0 -89
  247. data/test/dummy/config/environments/test.rb +0 -53
  248. data/test/dummy/config/initializers/content_security_policy.rb +0 -25
  249. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -8
  250. data/test/dummy/config/initializers/inflections.rb +0 -16
  251. data/test/dummy/config/locales/da.yml +0 -52
  252. data/test/dummy/config/locales/en.yml +0 -31
  253. data/test/dummy/config/puma.rb +0 -38
  254. data/test/dummy/config/routes.rb +0 -9
  255. data/test/dummy/config/storage.yml +0 -34
  256. data/test/dummy/config.ru +0 -6
  257. data/test/dummy/db/migrate/20251002183635_create_authors.rb +0 -11
  258. data/test/dummy/db/migrate/20251005131726_create_books.rb +0 -9
  259. data/test/dummy/db/migrate/20251005131811_create_titles.rb +0 -11
  260. data/test/dummy/db/migrate/20251031140958_add_author_books_join_table.rb +0 -9
  261. data/test/dummy/db/schema.rb +0 -44
  262. data/test/dummy/log/.keep +0 -0
  263. data/test/dummy/public/400.html +0 -114
  264. data/test/dummy/public/404.html +0 -114
  265. data/test/dummy/public/406-unsupported-browser.html +0 -114
  266. data/test/dummy/public/422.html +0 -114
  267. data/test/dummy/public/500.html +0 -114
  268. data/test/dummy/public/icon.png +0 -0
  269. data/test/dummy/public/icon.svg +0 -3
  270. data/test/dummy/storage/.keep +0 -0
  271. data/test/dummy/test/fixtures/authors.yml +0 -11
  272. data/test/dummy/test/models/author_test.rb +0 -7
  273. data/test/dummy/tmp/.keep +0 -0
  274. data/test/dummy/tmp/pids/.keep +0 -0
  275. data/test/dummy/tmp/storage/.keep +0 -0
  276. data/test/test_helper.rb +0 -15
  277. data/test/uchi/field_test.rb +0 -77
  278. data/test/uchi/i18n_test.rb +0 -18
  279. data/test/uchi/repository/routes_test.rb +0 -49
  280. data/test/uchi/repository/translate_test.rb +0 -272
  281. data/test/uchi/repository_test.rb +0 -137
  282. data/test/uchi/sort_order_test.rb +0 -47
  283. data/test/uchi_test.rb +0 -7
@@ -0,0 +1,14 @@
1
+ <div class="flex">
2
+ <div class="flex items-center h-5">
3
+ <%= input %>
4
+ </div>
5
+
6
+ <div class="ms-2 text-sm">
7
+ <%= label %>
8
+ <%= hint %>
9
+ </div>
10
+
11
+ <% errors.each do |error| %>
12
+ <%= render(Flowbite::Input::ValidationError.new) { error.upcase_first } %>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class RadioButton < InputField
6
+ def initialize(attribute:, form:, value:, disabled: false, hint: nil, input: {}, label: {})
7
+ super(attribute: attribute, form: form, disabled: disabled, hint: hint, input: input, label: label)
8
+ @value = value
9
+ end
10
+
11
+ protected
12
+
13
+ def default_input
14
+ args = {
15
+ attribute: @attribute,
16
+ disabled: disabled?,
17
+ form: @form,
18
+ options: default_input_options.merge(@input[:options] || {}),
19
+ value: @value
20
+ }
21
+
22
+ input_component.new(**args)
23
+ end
24
+
25
+ # Returns options for the default label element. This includes CSS classes
26
+ # since they are specific to RadioButton labels (and Checkbox ones).
27
+ def default_label_options
28
+ super.merge({
29
+ options: {
30
+ class: label_classes,
31
+ for: id_for_input_element
32
+ }
33
+ })
34
+ end
35
+
36
+ # Returns the HTML to use for the hint element if any
37
+ def hint
38
+ return unless hint?
39
+
40
+ component = Flowbite::Input::Hint.new(
41
+ attribute: @attribute,
42
+ form: @form,
43
+ options: {
44
+ class: hint_classes,
45
+ id: id_for_hint_element
46
+ }
47
+ ).with_content(@hint)
48
+ render(component)
49
+ end
50
+
51
+ def input_component
52
+ ::Flowbite::Input::RadioButton
53
+ end
54
+
55
+ private
56
+
57
+ def hint_classes
58
+ if disabled?
59
+ "text-xs font-normal text-gray-400 dark:text-gray-500"
60
+ else
61
+ "text-xs font-normal text-gray-500 dark:text-gray-300"
62
+ end
63
+ end
64
+
65
+ def id_for_input_element
66
+ [
67
+ @form.object_name,
68
+ @attribute,
69
+ @value
70
+ ].join("_")
71
+ end
72
+
73
+ def id_for_hint_element
74
+ [id_for_input_element, "hint"].join("_")
75
+ end
76
+
77
+ def label_classes
78
+ if disabled?
79
+ "font-medium text-gray-400 dark:text-gray-500"
80
+ else
81
+ "font-medium text-gray-900 dark:text-gray-300"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Select < InputField
6
+ def initialize(attribute:, form:, collection: [], disabled: false, hint: nil, input: {}, label: {}, size: :default)
7
+ super(attribute: attribute, disabled: disabled, form: form, hint: hint, input: input, label: label, size: size)
8
+ @collection = collection
9
+ end
10
+
11
+ def input
12
+ render(
13
+ input_component.new(
14
+ attribute: @attribute,
15
+ collection: @collection,
16
+ disabled: @disabled,
17
+ form: @form,
18
+ options: input_options,
19
+ size: @size
20
+ )
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def input_component
27
+ ::Flowbite::Input::Select
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Text < InputField
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Textarea < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Textarea
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Url < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Url
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # A form element for a single field, containing label, input field, error
5
+ # messages, helper text and whatever else is needed for a user friendly input
6
+ # experience.
7
+ #
8
+ # @see https://flowbite.com/docs/forms/input-field/
9
+ #
10
+ # The input field is an important part of the form element that can be used to
11
+ # create interactive controls to accept data from the user based on multiple
12
+ # input types, such as text, email, number, password, URL, phone number, and
13
+ # more.
14
+ #
15
+ # Usually you'd use one of the subclasses of this class which implement the
16
+ # different input types, like `Flowbite::InputField::Text`,
17
+ # `Flowbite::InputField::Email`, etc.
18
+ #
19
+ # Expects 2 arguments:
20
+ #
21
+ # @param attribute [Symbol] The name of the attribute to render in this input
22
+ # field.
23
+ #
24
+ # @param form [ActionView::Helpers::FormBuilder] The form builder object that
25
+ # will be used to generate the input field.
26
+ #
27
+ # Supports additional arguments:
28
+ #
29
+ # @param hint [String] A hint to display below the input field, providing
30
+ # additional context or instructions for the user. This is optional. See
31
+ # https://flowbite.com/docs/forms/input-field/#helper-text
32
+ #
33
+ # @param label [Hash] A hash with options for the label. These are passed to
34
+ # Flowbite::Input::Label, see that for details. Can contain:
35
+ # - `content`: The content of the label. If not provided, the label will
36
+ # default to the attribute name.
37
+ # - `options`: A hash of additional options to pass to the label component.
38
+ # This can be used to set the class, for example.
39
+ #
40
+ # @param disabled [Boolean] Whether the input field should be disabled.
41
+ # Defaults to `false`.
42
+ #
43
+ # @param input [Hash] A hash with options for the default input component.
44
+ # These are passed to the input components constructor, so see whatever
45
+ # component is being used for details. Can contain:
46
+ # - `options`: Additional HTML attributes to pass to the input element.
47
+ #
48
+ # @param size [Symbol] The size of the input field. Can be one of `:sm`,
49
+ # `:md`, or `:lg`. Defaults to `:md`.
50
+ #
51
+ # Sample usage
52
+ #
53
+ # <% form_for @person do |form| %>
54
+ # <%= render(
55
+ # Flowbite::InputField::Number.new(
56
+ # :attribute => :name,
57
+ # :form => form
58
+ # )
59
+ # ) %>
60
+ # <% end %>
61
+ #
62
+ # To render an input without labels or error messages etc, use
63
+ # `Flowbite::Input::Field` instead.
64
+ class InputField < ViewComponent::Base
65
+ renders_one :hint
66
+ renders_one :input
67
+ renders_one :label
68
+
69
+ # Returns the errors for attribute
70
+ def errors
71
+ @object.errors[@attribute] || []
72
+ end
73
+
74
+ def initialize(attribute:, form:, disabled: false, hint: nil, input: {}, label: {}, size: :default)
75
+ @attribute = attribute
76
+ @disabled = disabled
77
+ @form = form
78
+ @hint = hint
79
+ @input = input
80
+ @label = label
81
+ @object = form.object
82
+ @size = size
83
+ end
84
+
85
+ def input_component
86
+ ::Flowbite::Input::Field
87
+ end
88
+
89
+ protected
90
+
91
+ # Returns the HTML to use for the hint element if any
92
+ def default_hint
93
+ return unless hint?
94
+
95
+ component = Flowbite::Input::Hint.new(
96
+ attribute: @attribute,
97
+ form: @form,
98
+ options: default_hint_options
99
+ ).with_content(default_hint_content)
100
+ render(component)
101
+ end
102
+
103
+ def default_hint_content
104
+ return nil unless @hint
105
+
106
+ @hint[:content]
107
+ end
108
+
109
+ # Returns a Hash with the default attributes to apply to the hint element.
110
+ #
111
+ # The default attributes can be overriden by passing the `hint[options]`
112
+ # argument to the constructor.
113
+ def default_hint_options
114
+ return {} unless @hint
115
+
116
+ {
117
+ id: id_for_hint_element
118
+ }.merge(@hint[:options] || {})
119
+ end
120
+
121
+ # Returns a Hash with the default attributes to apply to the input element.
122
+ #
123
+ # The default attributes can be overriden by passing the `input[options]`
124
+ # argument to the constructor.
125
+ def default_input_options
126
+ if hint?
127
+ {
128
+ "aria-describedby": id_for_hint_element
129
+ }
130
+ else
131
+ {}
132
+ end
133
+ end
134
+
135
+ # Returns the HTML to use for the default input element.
136
+ def default_input
137
+ render(input_component.new(**input_arguments))
138
+ end
139
+
140
+ def default_label
141
+ component = Flowbite::Input::Label.new(**default_label_options)
142
+ if default_label_content
143
+ component.with_content(default_label_content)
144
+ else
145
+ component
146
+ end
147
+ end
148
+
149
+ def default_label_content
150
+ @label[:content]
151
+ end
152
+
153
+ def default_label_options
154
+ label_options = @label.dup
155
+ label_options.delete(:content)
156
+
157
+ {
158
+ attribute: @attribute,
159
+ form: @form
160
+ }.merge(label_options)
161
+ end
162
+
163
+ # Returns true if the input field is disabled, false otherwise.
164
+ def disabled?
165
+ !!@disabled
166
+ end
167
+
168
+ # Returns true if the input field has a hint, false otherwise.
169
+ def hint?
170
+ @hint.present?
171
+ end
172
+
173
+ def id_for_hint_element
174
+ "#{@form.object_name}_#{@attribute}_hint"
175
+ end
176
+
177
+ # @return [Hash] The keyword arguments for the input component.
178
+ def input_arguments
179
+ {
180
+ attribute: @attribute,
181
+ disabled: @disabled,
182
+ form: @form,
183
+ options: input_options,
184
+ size: @size
185
+ }
186
+ end
187
+
188
+ def input_options
189
+ default_input_options.merge(@input[:options] || {})
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,21 @@
1
+ module Flowbite
2
+ class Link < ViewComponent::Base
3
+ attr_reader :href, :text, :options
4
+
5
+ class << self
6
+ def classes
7
+ ["font-medium", "text-blue-600", "dark:text-blue-500", "hover:underline"].join(" ")
8
+ end
9
+ end
10
+
11
+ def initialize(href:, **options)
12
+ super()
13
+ @href = href
14
+ @options = options
15
+ end
16
+
17
+ def call
18
+ link_to(content, href, {class: self.class.classes}.merge(options))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class Style
5
+ attr_reader :classes
6
+
7
+ delegate :fetch, to: :classes
8
+
9
+ def initialize(classes)
10
+ @classes = classes
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ <div class="<%= container_classes.join(" ") %>">
2
+ <svg class="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
3
+ <path d="<%= svg_path %>"/>
4
+ </svg>
5
+ </div>
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class Toast
5
+ # Renders an icon for a toast notification.
6
+ #
7
+ # @param style [Symbol] The color style of the icon (:default, :success, :danger, :warning).
8
+ class Icon < ViewComponent::Base
9
+ class << self
10
+ def classes(style: :default)
11
+ styles.fetch(style).fetch(:classes)
12
+ end
13
+
14
+ def svg_path(style: :default)
15
+ styles.fetch(style).fetch(:svg_path)
16
+ end
17
+
18
+ # rubocop:disable Layout/LineLength
19
+ def styles
20
+ {
21
+ default: {
22
+ classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-blue-500", "bg-blue-100", "rounded-lg", "dark:bg-blue-800", "dark:text-blue-200"],
23
+ svg_path: "M15.147 15.085a7.159 7.159 0 0 1-6.189 3.307A6.713 6.713 0 0 1 3.1 15.444c-2.679-4.513.287-8.737.888-9.548A4.373 4.373 0 0 0 5 1.608c1.287.953 6.445 3.218 5.537 10.5 1.5-1.122 2.706-3.01 2.853-6.14 1.433 1.049 3.993 5.395 1.757 9.117Z"
24
+ },
25
+ success: {
26
+ classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-green-500", "bg-green-100", "rounded-lg", "dark:bg-green-800", "dark:text-green-200"],
27
+ svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"
28
+ },
29
+ danger: {
30
+ classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-red-500", "bg-red-100", "rounded-lg", "dark:bg-red-800", "dark:text-red-200"],
31
+ svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"
32
+ },
33
+ warning: {
34
+ classes: ["inline-flex", "items-center", "justify-center", "shrink-0", "w-8", "h-8", "text-orange-500", "bg-orange-100", "rounded-lg", "dark:bg-orange-700", "dark:text-orange-200"],
35
+ svg_path: "M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"
36
+ }
37
+ }.freeze
38
+ end
39
+ # rubocop:enable Layout/LineLength
40
+ end
41
+
42
+ attr_reader :style
43
+
44
+ def initialize(style: :default)
45
+ @style = style
46
+ end
47
+
48
+ def container_classes
49
+ self.class.classes(style: style)
50
+ end
51
+
52
+ def svg_path
53
+ self.class.svg_path(style: style)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,11 @@
1
+ <div class="<%= container_classes.join(" ") %>" role="alert" <%= options.map { |k, v| "#{k}=\"#{v}\"" }.join(" ").html_safe %>>
2
+ <%= render Flowbite::Toast::Icon.new(style: style) %>
3
+ <div class="ms-3 text-sm font-normal"><%= message %></div>
4
+ <% if dismissible %>
5
+ <button type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" aria-label="Close">
6
+ <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
7
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
8
+ </svg>
9
+ </button>
10
+ <% end %>
11
+ </div>
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Renders a toast notification element.
5
+ #
6
+ # See https://flowbite.com/docs/components/toast/
7
+ #
8
+ # @param message [String] The message to display in the toast.
9
+ # @param style [Symbol] The color style of the toast (:default, :success, :danger, :warning).
10
+ # @param dismissible [Boolean] Whether the toast can be dismissed (default: true).
11
+ # @param class [Array<String>] Additional CSS classes for the toast container.
12
+ # @param options [Hash] Additional HTML options for the toast container.
13
+ class Toast < ViewComponent::Base
14
+ class << self
15
+ def classes
16
+ ["flex", "items-center", "w-full", "max-w-xs", "p-4", "text-gray-500", "bg-white", "rounded-lg", "shadow-sm", "dark:text-gray-400", "dark:bg-gray-800"]
17
+ end
18
+ end
19
+
20
+ attr_reader :message, :style, :dismissible, :additional_classes, :options
21
+
22
+ def initialize(message:, style: :default, dismissible: true, class: [], **options)
23
+ @message = message
24
+ @style = style
25
+ @dismissible = dismissible
26
+ @additional_classes = Array(binding.local_variable_get(:class)) || []
27
+ @options = options
28
+ end
29
+
30
+ def container_classes
31
+ self.class.classes + additional_classes
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uchi
4
+ class Field
5
+ class Base < Field
6
+ # Uchi::Field::Base::Edit components render fields in the edit view.
7
+ class Edit < ViewComponent::Base
8
+ attr_reader :field, :form, :record, :repository, :label, :hint
9
+
10
+ def initialize(field:, form:, repository:, label: nil, hint: nil)
11
+ super()
12
+
13
+ @field = field
14
+ @form = form
15
+ @label = label
16
+ @hint = hint
17
+ @record = form.object
18
+ @repository = repository
19
+ end
20
+ end
21
+
22
+ # Uchi::Field::Base::Show components render fields in the show view.
23
+ class Show < ViewComponent::Base
24
+ attr_reader :field, :record, :repository
25
+
26
+ def initialize(field:, record:, repository:)
27
+ super()
28
+
29
+ @field = field
30
+ @record = record
31
+ @repository = repository
32
+ end
33
+ end
34
+
35
+ # Uchi::Field::Base::Index components render fields in the index view.
36
+ class Index < ViewComponent::Base
37
+ attr_reader :field, :record, :repository
38
+
39
+ class << self
40
+ # Returns the CSS classes to apply to the td or th of the table where
41
+ # this field is rendered.
42
+ def classes_for_table_cell
43
+ []
44
+ end
45
+ end
46
+
47
+ def initialize(field:, record:, repository:)
48
+ super()
49
+
50
+ @field = field
51
+ @record = record
52
+ @repository = repository
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1 @@
1
+ <%= render(Flowbite::InputField::Select.new(**options)) %>
@@ -0,0 +1 @@
1
+ <%= field.value(record) %>
@@ -0,0 +1,5 @@
1
+ <% if associated_record %>
2
+ <%= render(Flowbite::Link.new(
3
+ href: associated_repository.routes.path_for(:show, id: associated_record.id)
4
+ ).with_content(associated_record.to_s)) %>
5
+ <% end %>