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,17 @@
1
+ /* Uchi UI */
2
+
3
+ /* This file is compiled into app/assets/builds/uchi.css by
4
+ tailwindcss running tailwindcss --watch in Procfile.dev */
5
+
6
+ /* We need tailwindcss, duh */
7
+ @import "tailwindcss";
8
+
9
+ /* Import the default theme variables from Flowbite
10
+ This imports them from node_modules/flowbite/src/themes/default.css */
11
+ @import "flowbite/src/themes/default";
12
+
13
+ /* Import the Flowbite plugin */
14
+ @plugin "flowbite/plugin";
15
+
16
+ /* Configure the source files of Flowbite */
17
+ @source "../../../node_modules/flowbite";
@@ -0,0 +1,21 @@
1
+ /* Uchi UI */
2
+
3
+ /* This file is compiled into app/assets/stylesheets/uchi/application.css by
4
+ running the following command:
5
+
6
+ tailwindcss -i app/assets/tailwind/uchi.css -o app/assets/stylesheets/uchi/application.css
7
+ npx @tailwindcss/cli -i ./app/assets/tailwind/uchi.css -o ./app/assets/stylesheets/uchi/application.css
8
+ */
9
+
10
+ /* We need tailwindcss, duh */
11
+ @import "tailwindcss";
12
+
13
+ /* Import the default theme variables from Flowbite
14
+ This imports them from node_modules/flowbite/src/themes/default.css */
15
+ @import "flowbite/src/themes/default";
16
+
17
+ /* Import the Flowbite plugin */
18
+ @plugin "flowbite/plugin";
19
+
20
+ /* Configure the source files of Flowbite */
21
+ @source "../../../node_modules/flowbite";
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Renders a breadcrumb navigation component.
5
+ #
6
+ # See https://flowbite.com/docs/components/breadcrumb/
7
+ #
8
+ # @example Basic usage with BreadcrumbItem components
9
+ # <%= render Flowbite::Breadcrumb.new do |breadcrumb| %>
10
+ # <% breadcrumb.with_item do %>
11
+ # <%= render Flowbite::BreadcrumbItem::First.new(href: "/") { "Home" } %>
12
+ # <% end %>
13
+ # <% breadcrumb.with_item do %>
14
+ # <%= render Flowbite::BreadcrumbItem.new(href: "/projects") { "Projects" } %>
15
+ # <% end %>
16
+ # <% breadcrumb.with_item do %>
17
+ # <%= render Flowbite::BreadcrumbItem::Current.new { "Current Page" } %>
18
+ # <% end %>
19
+ # <% end %>
20
+ class Breadcrumb < ViewComponent::Base
21
+ renders_many :items
22
+
23
+ def call
24
+ content_tag(:nav, class: "flex", "aria-label": "Breadcrumb") do
25
+ content_tag(:ol, class: "inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse") do
26
+ items.each do |item|
27
+ concat(item)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Renders a breadcrumb home icon.
5
+ #
6
+ # This is typically used as a prefix icon in the first breadcrumb item,
7
+ # but can be used standalone if needed.
8
+ #
9
+ # @example Standalone usage
10
+ # <%= render Flowbite::BreadcrumbHome.new %>
11
+ class BreadcrumbHome < ViewComponent::Base
12
+ def call
13
+ tag.svg(
14
+ class: "w-3 h-3 me-2.5",
15
+ "aria-hidden": "true",
16
+ xmlns: "http://www.w3.org/2000/svg",
17
+ fill: "currentColor",
18
+ viewBox: "0 0 20 20"
19
+ ) do
20
+ tag.path(
21
+ d: "m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class BreadcrumbItem
5
+ # Renders the current page breadcrumb item.
6
+ # Current items are rendered as non-interactive spans with different styling.
7
+ #
8
+ # @param options [Hash] Additional HTML attributes to pass to the span element.
9
+ #
10
+ # @example Current page item
11
+ # <%= render Flowbite::BreadcrumbItem::Current.new { "Current Page" } %>
12
+ class Current < BreadcrumbItem
13
+ def initialize(**options)
14
+ super(href: nil, **options)
15
+ end
16
+
17
+ protected
18
+
19
+ def item_options
20
+ {"aria-current": "page"}
21
+ end
22
+
23
+ def render_link
24
+ link_options = {class: link_classes}.merge(options)
25
+ content_tag(:span, content, **link_options)
26
+ end
27
+
28
+ def link_classes
29
+ ["ms-1", "text-sm", "font-medium", "text-gray-500", "dark:text-gray-400"]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class BreadcrumbItem
5
+ # Renders the first breadcrumb item (typically home).
6
+ # First items don't show a separator icon.
7
+ #
8
+ # @param href [String] The URL for the breadcrumb link.
9
+ # @param options [Hash] Additional HTML attributes to pass to the link element.
10
+ #
11
+ # @example First item
12
+ # <%= render Flowbite::BreadcrumbItem::First.new(href: "/") { "Home" } %>
13
+ class First < BreadcrumbItem
14
+ protected
15
+
16
+ def item_options
17
+ {class: "inline-flex items-center"}
18
+ end
19
+
20
+ def link_classes
21
+ ["text-sm", "font-medium", "inline-flex", "items-center", "text-gray-700", "hover:text-blue-600", "dark:text-gray-400", "dark:hover:text-white"]
22
+ end
23
+
24
+ def prefix_icon
25
+ nil
26
+ end
27
+
28
+ def render_link
29
+ icon = render(Flowbite::BreadcrumbHome.new)
30
+ link_options = {class: link_classes}.merge(options)
31
+ content_tag(:a, safe_join([icon, content]), href: href, **link_options)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Base class for rendering a breadcrumb item (middle items in the breadcrumb trail).
5
+ #
6
+ # @param href [String] The URL for the breadcrumb link.
7
+ # @param options [Hash] Additional HTML attributes to pass to the link element.
8
+ #
9
+ # @example Middle item
10
+ # <%= render Flowbite::BreadcrumbItem.new(href: "/projects") { "Projects" } %>
11
+ class BreadcrumbItem < ViewComponent::Base
12
+ attr_reader :href, :options
13
+
14
+ def initialize(href:, **options)
15
+ super()
16
+ @href = href
17
+ @options = options
18
+ end
19
+
20
+ def call
21
+ content_tag(:li, item_options) do
22
+ content_tag(:div, class: "flex items-center") do
23
+ concat(render(prefix_icon)) if prefix_icon
24
+ concat(render_link)
25
+ end
26
+ end
27
+ end
28
+
29
+ protected
30
+
31
+ def item_options
32
+ {}
33
+ end
34
+
35
+ def prefix_icon
36
+ Flowbite::BreadcrumbSeparator.new
37
+ end
38
+
39
+ def render_link
40
+ link_options = {class: link_classes}.merge(options)
41
+ content_tag(:a, content, href: href, **link_options)
42
+ end
43
+
44
+ def link_classes
45
+ ["ms-1", "text-sm", "font-medium", "text-gray-700", "hover:text-blue-600", "dark:text-gray-400", "dark:hover:text-white"]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Renders a breadcrumb separator icon.
5
+ #
6
+ # This is automatically used by BreadcrumbItem components, but can be
7
+ # used standalone if needed.
8
+ #
9
+ # @example Standalone usage
10
+ # <%= render Flowbite::BreadcrumbSeparator.new %>
11
+ class BreadcrumbSeparator < ViewComponent::Base
12
+ def call
13
+ tag.svg(
14
+ class: "rtl:rotate-180 w-3 h-3 text-gray-400 mx-1",
15
+ "aria-hidden": "true",
16
+ xmlns: "http://www.w3.org/2000/svg",
17
+ fill: "none",
18
+ viewBox: "0 0 6 10"
19
+ ) do
20
+ tag.path(
21
+ stroke: "currentColor",
22
+ "stroke-linecap": "round",
23
+ "stroke-linejoin": "round",
24
+ "stroke-width": "2",
25
+ d: "m1 9 4-4-4-4"
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class Button
5
+ class Outline < Flowbite::Button
6
+ class << self
7
+ # rubocop:disable Layout/LineLength
8
+ def styles
9
+ {
10
+ default: Flowbite::Style.new(
11
+ default: ["text-blue-700", "hover:text-white", "border", "border-blue-700", "hover:bg-blue-800", "focus:ring-4", "focus:outline-none", "focus:ring-blue-300", "font-medium", "rounded-lg", "text-center", "me-2", "mb-2", "dark:border-blue-500", "dark:text-blue-500", "dark:hover:text-white", "dark:hover:bg-blue-500", "dark:focus:ring-blue-800"]
12
+ ),
13
+ green: Flowbite::Style.new(
14
+ default: ["text-green-700", "hover:text-white", "border", "border-green-700", "hover:bg-green-800", "focus:ring-4", "focus:outline-none", "focus:ring-green-300", "font-medium", "rounded-lg", "text-center", "me-2", "mb-2", "dark:border-green-500", "dark:text-green-500", "dark:hover:text-white", "dark:hover:bg-green-600", "dark:focus:ring-green-800"]
15
+ )
16
+ }
17
+ end
18
+ # rubocop:enable Layout/LineLength
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class Button
5
+ class Pill < Flowbite::Button
6
+ class << self
7
+ # rubocop:disable Layout/LineLength, Metrics/MethodLength
8
+ def styles
9
+ {
10
+ alternative: Flowbite::Style.new(
11
+ default: ["text-sm", "font-medium", "text-gray-900", "focus:outline-none", "bg-white", "rounded-full", "border", "border-gray-200", "hover:bg-gray-100", "hover:text-blue-700", "focus:z-10", "focus:ring-4", "focus:ring-gray-100", "dark:focus:ring-gray-700", "dark:bg-gray-800", "dark:text-gray-400", "dark:border-gray-600", "dark:hover:text-white", "dark:hover:bg-gray-700"]
12
+ ),
13
+ dark: Flowbite::Style.new(
14
+ default: ["text-white", "bg-gray-800", "hover:bg-gray-900", "focus:outline-none", "focus:ring-4", "focus:ring-gray-300", "font-medium", "rounded-full", "dark:bg-gray-800", "dark:hover:bg-gray-700", "dark:focus:ring-gray-700", "dark:border-gray-700"]
15
+ ),
16
+ default: Flowbite::Style.new(
17
+ default: ["text-white", "bg-blue-700", "hover:bg-blue-800", "focus:outline-none", "focus:ring-4", "focus:ring-blue-300", "font-medium", "rounded-full", "text-center", "dark:bg-blue-600", "dark:hover:bg-blue-700", "dark:focus:ring-blue-800"]
18
+ ),
19
+ green: Flowbite::Style.new(
20
+ default: ["text-white", "bg-green-700", "hover:bg-green-800", "focus:outline-none", "focus:ring-4", "focus:ring-green-300", "font-medium", "rounded-full", "text-center", "dark:bg-green-600", "dark:hover:bg-green-700", "dark:focus:ring-green-800"]
21
+ ),
22
+ light: Flowbite::Style.new(
23
+ default: ["text-gray-900", "bg-white", "border", "border-gray-300", "focus:outline-none", "hover:bg-gray-100", "focus:ring-4", "focus:ring-gray-100", "font-medium", "rounded-full", "dark:bg-gray-800", "dark:text-white", "dark:border-gray-600", "dark:hover:bg-gray-700", "dark:hover:border-gray-600", "dark:focus:ring-gray-700"]
24
+ ),
25
+ purple: Flowbite::Style.new(
26
+ default: ["text-white", "bg-purple-700", "hover:bg-purple-800", "focus:outline-none", "focus:ring-4", "focus:ring-purple-300", "font-medium", "rounded-full", "text-center", "dark:bg-purple-600", "dark:hover:bg-purple-700", "dark:focus:ring-purple-900"]
27
+ ),
28
+ red: Flowbite::Style.new(
29
+ default: ["text-white", "bg-red-700", "hover:bg-red-800", "focus:outline-none", "focus:ring-4", "focus:ring-red-300", "font-medium", "rounded-full", "text-center", "dark:bg-red-600", "dark:hover:bg-red-700", "dark:focus:ring-red-900"]
30
+ ),
31
+ yellow: Flowbite::Style.new(
32
+ default: ["text-white", "bg-yellow-400", "hover:bg-yellow-500", "focus:outline-none", "focus:ring-4", "focus:ring-yellow-300", "font-medium", "rounded-full", "text-center", "dark:focus:ring-yellow-900"]
33
+ )
34
+ }
35
+ end
36
+ # rubocop:enable Layout/LineLength, Metrics/MethodLength
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Renders a HTML button element.
5
+ #
6
+ # See https://flowbite.com/docs/components/buttons/
7
+ #
8
+ # @param label [String] The text to display on the button.
9
+ #
10
+ # All other parameters are optional and are passed directly to the button tag
11
+ # as HTML attributes.
12
+ class Button < ViewComponent::Base
13
+ SIZES = {
14
+ xs: ["text-xs", "px-3", "py-2"],
15
+ sm: ["text-sm", "px-3", "py-2"],
16
+ default: ["text-sm", "px-5", "py-2.5"],
17
+ lg: ["text-base", "px-5", "py-3"],
18
+ xl: ["text-base", "px-6", "py-3.5"]
19
+ }.freeze
20
+
21
+ class << self
22
+ def classes(size: :default, state: :default, style: :default)
23
+ style = styles.fetch(style)
24
+ classes = style.fetch(state)
25
+ classes + sizes.fetch(size)
26
+ end
27
+
28
+ def sizes
29
+ SIZES
30
+ end
31
+
32
+ # rubocop:disable Layout/LineLength
33
+ def styles
34
+ {
35
+ alternative: Flowbite::Style.new(
36
+ default: ["font-medium", "text-gray-900", "focus:outline-none", "bg-white", "rounded-lg", "border", "border-gray-200", "hover:bg-gray-100", "hover:text-blue-700", "focus:z-10", "focus:ring-4", "focus:ring-gray-100", "dark:focus:ring-gray-700", "dark:bg-gray-800", "dark:text-gray-400", "dark:border-gray-600", "dark:hover:text-white", "dark:hover:bg-gray-700"]
37
+ ),
38
+ dark: Flowbite::Style.new(
39
+ default: ["text-white", "bg-gray-800", "hover:bg-gray-900", "focus:ring-4", "focus:ring-gray-300", "font-medium", "rounded-lg", "dark:bg-gray-800", "dark:hover:bg-gray-700", "dark:focus:ring-gray-700", "dark:border-gray-700"]
40
+ ),
41
+ default: Flowbite::Style.new(
42
+ default: ["text-white", "bg-blue-700", "hover:bg-blue-800", "focus:ring-4", "focus:ring-blue-300", "font-medium", "rounded-lg", "dark:bg-blue-600", "dark:hover:bg-blue-700", "focus:outline-none", "dark:focus:ring-blue-800"]
43
+ ),
44
+ green: Flowbite::Style.new(
45
+ default: ["focus:outline-none", "text-white", "bg-green-700", "hover:bg-green-800", "focus:ring-4", "focus:ring-green-300", "font-medium", "rounded-lg", "dark:bg-green-600", "dark:hover:bg-green-700", "dark:focus:ring-green-800"]
46
+ ),
47
+ light: Flowbite::Style.new(
48
+ default: ["text-gray-900", "bg-white", "border", "border-gray-300", "hover:bg-gray-100", "focus:ring-4", "focus:ring-gray-100", "font-medium", "rounded-lg", "dark:bg-gray-800", "dark:text-white", "dark:border-gray-600", "dark:hover:bg-gray-700", "dark:hover:border-gray-600", "dark:focus:ring-gray-700"]
49
+ ),
50
+ purple: Flowbite::Style.new(
51
+ default: ["focus:outline-none", "text-white", "bg-purple-700", "hover:bg-purple-800", "focus:ring-4", "focus:ring-purple-300", "font-medium", "rounded-lg", "dark:bg-purple-600", "dark:hover:bg-purple-700", "dark:focus:ring-purple-900"]
52
+ ),
53
+ red: Flowbite::Style.new(
54
+ default: ["focus:outline-none", "text-white", "bg-red-700", "hover:bg-red-800", "focus:ring-4", "focus:ring-red-300", "font-medium", "rounded-lg", "dark:bg-red-600", "dark:hover:bg-red-700", "dark:focus:ring-red-900"]
55
+ ),
56
+ yellow: Flowbite::Style.new(
57
+ default: ["focus:outline-none", "text-white", "bg-yellow-400", "hover:bg-yellow-500", "focus:ring-4", "focus:ring-yellow-300", "font-medium", "rounded-lg", "dark:focus:ring-yellow-900"]
58
+ )
59
+ }.freeze
60
+ end
61
+ # rubocop:enable Layout/LineLength
62
+ end
63
+
64
+ attr_reader :button_attributes, :size, :style
65
+
66
+ def initialize(size: :default, style: :default, **button_attributes)
67
+ @size = size
68
+ @style = style
69
+ @button_attributes = button_attributes
70
+ end
71
+
72
+ def call
73
+ content_tag(
74
+ :button,
75
+ content,
76
+ **options
77
+ )
78
+ end
79
+
80
+ private
81
+
82
+ def classes
83
+ self.class.classes(size: size, state: :default, style: style)
84
+ end
85
+
86
+ def options
87
+ {
88
+ class: classes
89
+ }.merge(button_attributes)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ # Renders a card element.
5
+ #
6
+ # See https://flowbite.com/docs/components/cards/
7
+ class Card < ViewComponent::Base
8
+ class << self
9
+ def classes(state: :default, style: :default)
10
+ style = styles.fetch(style)
11
+ style.fetch(state)
12
+ end
13
+
14
+ # rubocop:disable Layout/LineLength
15
+ def styles
16
+ {
17
+ default: Flowbite::Style.new(
18
+ default: ["p-6", "bg-white", "border", "border-gray-200", "rounded-lg", "shadow-sm", "dark:bg-gray-800", "dark:border-gray-700"]
19
+ )
20
+ }.freeze
21
+ end
22
+ # rubocop:enable Layout/LineLength
23
+ end
24
+
25
+ def call
26
+ card_options = {}
27
+ card_options[:class] = self.class.classes + @class
28
+
29
+ content_tag(:div, card_options.merge(@options)) do
30
+ concat(content_tag(:div, content, class: "font-normal text-gray-700 dark:text-gray-400"))
31
+ end
32
+ end
33
+
34
+ # @param class [Array<String>] Additional CSS classes for the card
35
+ # container.
36
+ #
37
+ # @param options [Hash] Additional HTML options for the card container
38
+ # (e.g., custom classes, data attributes). These options are merged into
39
+ # the card's root element.
40
+ def initialize(class: [], options: {})
41
+ @class = Array(binding.local_variable_get(:class)) || []
42
+ @options = options || {}
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ # The checkbox component can be used to receive one or more selected options
6
+ # from the user in the form of a square box available in multiple styles,
7
+ # sizes, colors, and variants coded with the utility classes from Tailwind
8
+ # CSS and with support for dark mode.
9
+ #
10
+ # https://flowbite.com/docs/forms/checkbox/
11
+ class Checkbox < Field
12
+ DEFAULT_CHECKED_VALUE = "1"
13
+ DEFAULT_UNCHECKED_VALUE = "0"
14
+
15
+ class << self
16
+ # Checkboxes only have their default size.
17
+ def sizes
18
+ {
19
+ default: ["w-4", "h-4"]
20
+ }
21
+ end
22
+
23
+ # rubocop:disable Layout/LineLength
24
+ def styles
25
+ {
26
+ default: Flowbite::Style.new(
27
+ default: ["text-blue-600", "bg-gray-100", "border-gray-300", "rounded-sm", "focus:ring-blue-500", "dark:focus:ring-blue-600", "dark:ring-offset-gray-800", "focus:ring-2", "dark:bg-gray-700", "dark:border-gray-600"],
28
+ disabled: ["text-blue-600", "bg-gray-100", "border-gray-300", "rounded-sm", "focus:ring-blue-500", "dark:focus:ring-blue-600", "dark:ring-offset-gray-800", "focus:ring-2", "dark:bg-gray-700", "dark:border-gray-600"],
29
+ error: ["text-red-600", "bg-red-50", "border-red-500", "rounded-sm", "focus:ring-red-500", "dark:focus:ring-red-600", "dark:ring-offset-gray-800", "focus:ring-2", "dark:bg-gray-700", "dark:border-red-500"]
30
+ )
31
+ }.freeze
32
+ end
33
+ end
34
+
35
+ # Returns the HTML to use for the actual input field element.
36
+ def call
37
+ @form.send(
38
+ input_field_type,
39
+ @attribute,
40
+ input_options,
41
+ checked_value,
42
+ unchecked_value
43
+ )
44
+ end
45
+
46
+ def initialize(attribute:, form:, disabled: false, options: {}, size: :default, unchecked_value: DEFAULT_UNCHECKED_VALUE, value: DEFAULT_CHECKED_VALUE)
47
+ super(attribute: attribute, form: form, disabled: disabled, options: options, size: size)
48
+ @unchecked_value = unchecked_value
49
+ @value = value
50
+ end
51
+
52
+ def input_field_type
53
+ :check_box
54
+ end
55
+
56
+ # Returns the options argument for the input field
57
+ def input_options
58
+ {
59
+ class: classes,
60
+ disabled: disabled?
61
+ }.merge(options)
62
+ end
63
+
64
+ private
65
+
66
+ def checked_value
67
+ @value
68
+ end
69
+
70
+ attr_reader :unchecked_value
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class Date < Field
6
+ def input_field_type
7
+ :date_field
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class DateTime < Field
6
+ def input_field_type
7
+ :datetime_field
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class Email < Field
6
+ # Returns the name of the method used to generate HTML for the input field
7
+ def input_field_type
8
+ :email_field
9
+ end
10
+ end
11
+ end
12
+ end