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,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ # The indivdual input component for use in forms without labels or error
6
+ # messages.
7
+ #
8
+ # Use this when you want to render an input field on its own without any
9
+ # surrounding elements, ie as a building block in more complex input
10
+ # components.
11
+ #
12
+ # To render a complete input field with labels and error messages, use
13
+ # `Flowbite::InputField` instead.
14
+ class Field < ViewComponent::Base
15
+ SIZES = {
16
+ sm: ["p-2", "text-xs"],
17
+ default: ["p-2.5", "text-sm"],
18
+ lg: ["p-4", "text-base"]
19
+ }.freeze
20
+
21
+ STATES = [
22
+ DEFAULT = :default,
23
+ DISABLED = :disabled,
24
+ ERROR = :error
25
+ ].freeze
26
+
27
+ attr_reader :options, :size, :style
28
+
29
+ class << self
30
+ def classes(size: :default, state: :default, style: :default)
31
+ style = styles.fetch(style)
32
+ state_classes = style.fetch(state)
33
+ state_classes + sizes.fetch(size)
34
+ end
35
+
36
+ # Returns the sizes this Field supports.
37
+ #
38
+ # This is effectively the SIZES constant, but provided as a method to
39
+ # return the constant from the current class, not the superclass.
40
+ #
41
+ # @return [Hash] A hash mapping size names to their corresponding CSS
42
+ # classes.
43
+ def sizes
44
+ const_get(:SIZES)
45
+ end
46
+
47
+ # rubocop:disable Layout/LineLength
48
+ def styles
49
+ {
50
+ default: Flowbite::Style.new(
51
+ default: ["bg-gray-50", "border", "border-gray-300", "text-gray-900", "rounded-lg", "focus:ring-blue-500", "focus:border-blue-500", "block", "w-full", "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"],
52
+ disabled: ["bg-gray-100", "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", "cursor-not-allowed", "dark:bg-gray-700", "dark:border-gray-600", "dark:placeholder-gray-400", "dark:text-gray-400", "dark:focus:ring-blue-500", "dark:focus:border-blue-500"],
53
+ error: ["bg-red-50", "border", "border-red-500", "text-red-900", "placeholder-red-700", "rounded-lg", "focus:ring-red-500", "dark:bg-gray-700", "focus:border-red-500", "block", "w-full", "dark:text-red-500", "dark:placeholder-red-500", "dark:border-red-500"]
54
+ )
55
+ }.freeze
56
+ end
57
+ # rubocop:enable Layout/LineLength
58
+ end
59
+
60
+ def initialize(attribute:, form:, disabled: false, options: {}, size: :default)
61
+ @attribute = attribute
62
+ @disabled = disabled
63
+ @form = form
64
+ @options = options || {}
65
+ @object = form.object
66
+ @size = size
67
+ end
68
+
69
+ # Returns the HTML to use for the actual input field element.
70
+ def call
71
+ @form.send(
72
+ input_field_type,
73
+ @attribute,
74
+ **input_options
75
+ )
76
+ end
77
+
78
+ # Returns the CSS classes to apply to the input field
79
+ def classes
80
+ self.class.classes(size: size, state: state)
81
+ end
82
+
83
+ # Returns the name of the method used to generate HTML for the input field
84
+ def input_field_type
85
+ :text_field
86
+ end
87
+
88
+ protected
89
+
90
+ # Returns true if the field is disabled
91
+ def disabled?
92
+ !!@disabled
93
+ end
94
+
95
+ def errors?
96
+ @object.errors.include?(@attribute.intern)
97
+ end
98
+
99
+ private
100
+
101
+ # Returns the options argument for the input field
102
+ def input_options
103
+ {
104
+ class: classes,
105
+ disabled: disabled?
106
+ }.merge(options)
107
+ end
108
+
109
+ def state
110
+ return DISABLED if disabled?
111
+ return ERROR if errors?
112
+
113
+ DEFAULT
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class File < Field
6
+ SIZES = {
7
+ sm: ["text-xs"],
8
+ default: ["text-sm"],
9
+ lg: ["text-lg"]
10
+ }.freeze
11
+
12
+ # Returns the name of the method used to generate HTML for the input field
13
+ def input_field_type
14
+ :file_field
15
+ end
16
+
17
+ # rubocop:disable Layout/LineLength
18
+ def self.styles
19
+ {
20
+ default: Flowbite::Style.new(
21
+ default: ["block", "w-full", "text-gray-900", "border", "border-gray-300", "rounded-lg", "cursor-pointer", "bg-gray-50", "focus:outline-none", "dark:text-gray-400", "dark:bg-gray-700", "dark:border-gray-600"],
22
+ disabled: ["block", "w-full", "text-gray-400", "border", "border-gray-300", "rounded-lg", "cursor-not-allowed", "bg-gray-100", "dark:text-gray-500", "dark:bg-gray-600", "dark:border-gray-500"],
23
+ error: ["block", "w-full", "text-red-900", "border", "border-red-500", "rounded-lg", "cursor-pointer", "bg-red-50", "focus:outline-none", "dark:text-red-400", "dark:bg-gray-700", "dark:border-red-500"]
24
+ )
25
+ }.freeze
26
+ end
27
+ # rubocop:enable Layout/LineLength
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class Hint < ViewComponent::Base
6
+ STATES = [
7
+ DEFAULT = :default
8
+ ].freeze
9
+
10
+ class << self
11
+ def classes(state: :default, style: :default)
12
+ style = styles.fetch(style)
13
+ style.fetch(state)
14
+ end
15
+
16
+ def styles
17
+ {
18
+ default: Flowbite::Style.new(
19
+ default: ["mt-2", "text-sm", "text-gray-500", "dark:text-gray-400"]
20
+ )
21
+ }.freeze
22
+ end
23
+ end
24
+
25
+ def call
26
+ tag.p(
27
+ content,
28
+ class: classes,
29
+ **@options
30
+ )
31
+ end
32
+
33
+ def initialize(attribute:, form:, options: {})
34
+ @attribute = attribute
35
+ @form = form
36
+ @options = options
37
+ @object = form.object
38
+ end
39
+
40
+ # Returns an array with the CSS classes to apply to the label element
41
+ def classes
42
+ self.class.classes(state: state)
43
+ end
44
+
45
+ protected
46
+
47
+ # Returns the state of the label.
48
+ #
49
+ # See the STATES constant for valid values.
50
+ #
51
+ # @return [Symbol] the state of the label
52
+ def state
53
+ DEFAULT
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ # https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-label
6
+ class Label < ViewComponent::Base
7
+ STATES = [
8
+ DEFAULT = :default,
9
+ DISABLED = :disabled,
10
+ ERROR = :error
11
+ ].freeze
12
+
13
+ class << self
14
+ def classes(state: :default, style: :default)
15
+ style = styles.fetch(style)
16
+ style.fetch(state)
17
+ end
18
+
19
+ def styles
20
+ {
21
+ default: Flowbite::Style.new(
22
+ default: ["block", "mb-2", "text-sm", "font-medium", "text-gray-900", "dark:text-white"],
23
+ disabled: ["block", "mb-2", "text-sm", "font-medium", "text-gray-400", "dark:text-gray-500"],
24
+ error: ["block", "mb-2", "text-sm", "font-medium", "text-red-700", "dark:text-red-500"]
25
+ )
26
+ }.freeze
27
+ end
28
+ end
29
+
30
+ def call
31
+ if content?
32
+ @form.label(@attribute, content, **options)
33
+ else
34
+ @form.label(@attribute, **options)
35
+ end
36
+ end
37
+
38
+ def errors?
39
+ @object.errors.include?(@attribute.intern)
40
+ end
41
+
42
+ def initialize(attribute:, form:, disabled: false, options: {})
43
+ @attribute = attribute
44
+ @disabled = disabled
45
+ @form = form
46
+ @object = form.object
47
+ @options = options
48
+ end
49
+
50
+ # Returns an array with the CSS classes to apply to the label element
51
+ def classes
52
+ self.class.classes(state: state)
53
+ end
54
+
55
+ protected
56
+
57
+ def disabled?
58
+ !!@disabled
59
+ end
60
+
61
+ # Returns the state of the label.
62
+ #
63
+ # See the STATES constant for valid values.
64
+ #
65
+ # @return [Symbol] the state of the label
66
+ def state
67
+ return DISABLED if disabled?
68
+ return ERROR if errors?
69
+
70
+ DEFAULT
71
+ end
72
+
73
+ private
74
+
75
+ def options
76
+ {
77
+ class: classes
78
+ }.merge(@options)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class Number < Field
6
+ def input_field_type
7
+ :number_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 Password < Field
6
+ def input_field_type
7
+ :password_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 Phone < Field
6
+ def input_field_type
7
+ :phone_field
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ # The radio button component can be used to allow the user to choose a
6
+ # single option from one or more available options.
7
+ #
8
+ # https://flowbite.com/docs/forms/radio/
9
+ class RadioButton < Field
10
+ class << self
11
+ # Radio buttons only have their default size.
12
+ def sizes
13
+ {
14
+ default: ["w-4", "h-4"]
15
+ }
16
+ end
17
+
18
+ # rubocop:disable Layout/LineLength
19
+ def styles
20
+ {
21
+ default: Flowbite::Style.new(
22
+ default: ["text-blue-600", "bg-gray-100", "border-gray-300", "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"],
23
+ disabled: ["text-blue-600", "bg-gray-100", "border-gray-300", "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"],
24
+ error: ["text-red-600", "bg-red-50", "border-red-500", "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"]
25
+ )
26
+ }.freeze
27
+ end
28
+ end
29
+
30
+ # Returns the HTML to use for the actual input field element.
31
+ def call
32
+ @form.send(
33
+ input_field_type,
34
+ @attribute,
35
+ @value,
36
+ **input_options
37
+ )
38
+ end
39
+
40
+ def initialize(attribute:, form:, value:, disabled: false, options: {})
41
+ super(attribute: attribute, disabled: disabled, form: form, options: options)
42
+ @value = value
43
+ end
44
+
45
+ def input_field_type
46
+ :radio_button
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ # The `Select` component renders a select input field for use in forms.
6
+ #
7
+ # https://flowbite.com/docs/forms/select/
8
+ #
9
+ # Wraps `ActionView::Helpers::FormOptionsHelper#select`: https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-select
10
+ class Select < Field
11
+ SIZES = {
12
+ sm: ["p-2", "text-xs"],
13
+ default: ["p-2.5", "text-sm"],
14
+ lg: ["px-4", "py-3", "text-base"]
15
+ }.freeze
16
+
17
+ def initialize(form:, attribute:, collection: [], disabled: false, options: {}, size: :default)
18
+ super(form: form, attribute: attribute, disabled: disabled, options: options, size: size)
19
+ @collection = collection
20
+ end
21
+
22
+ # Returns the HTML to use for the actual input field element.
23
+ def call
24
+ @form.send(
25
+ input_field_type,
26
+ @attribute,
27
+ @collection,
28
+ {},
29
+ html_options
30
+ )
31
+ end
32
+
33
+ # Returns the name of the method used to generate HTML for the input field
34
+ def input_field_type
35
+ :select
36
+ end
37
+
38
+ private
39
+
40
+ # Returns the html_options argument for the select method
41
+ def html_options
42
+ {
43
+ class: classes,
44
+ disabled: disabled?
45
+ }.merge(options)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class Textarea < Field
6
+ class << self
7
+ # rubocop:disable Layout/LineLength
8
+ def styles
9
+ {
10
+ default: Flowbite::Style.new(
11
+ default: ["block", "w-full", "text-gray-900", "bg-gray-50", "rounded-lg", "border", "border-gray-300", "focus:ring-blue-500", "focus:border-blue-500", "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"],
12
+ disabled: ["block", "w-full", "bg-gray-100", "rounded-lg", "border", "border-gray-300", "text-gray-900", "cursor-not-allowed"],
13
+ error: ["block", "w-full", "bg-red-50", "border", "border-red-500", "text-red-900", "placeholder-red-700", "rounded-lg", "focus:ring-red-500", "focus:border-red-500", "dark:bg-gray-700", "dark:text-red-500", "dark:placeholder-red-500", "dark:border-red-500"]
14
+ )
15
+ }.freeze
16
+ end
17
+ # rubocop:enable Layout/LineLength
18
+ end
19
+
20
+ # Returns the HTML to use for the actual input field element.
21
+ def call
22
+ @form.send(
23
+ input_field_type,
24
+ @attribute,
25
+ **input_options
26
+ )
27
+ end
28
+
29
+ protected
30
+
31
+ # Returns the CSS classes to apply to the input field
32
+ def classes
33
+ self.class.classes(size: size, state: state)
34
+ end
35
+
36
+ # Returns the name of the method used to generate HTML for the input field
37
+ def input_field_type
38
+ :text_area
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class Url < Field
6
+ # Returns the name of the method used to generate HTML for the input field
7
+ def input_field_type
8
+ :url_field
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ module Input
5
+ class ValidationError < ViewComponent::Base
6
+ def call
7
+ tag.p(content, class: "mt-2 text-sm text-red-600 dark:text-red-500")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Checkbox < InputField
6
+ protected
7
+
8
+ def default_hint_options
9
+ return {} unless @hint
10
+
11
+ {
12
+ class: hint_classes,
13
+ id: id_for_hint_element
14
+ }.merge(@hint[:options] || {})
15
+ end
16
+
17
+ def default_label_options
18
+ options = super
19
+ options[:options] ||= {}
20
+ options[:options][:class] = options.dig(:options, :class) || label_classes
21
+ options
22
+ end
23
+
24
+ def input_component
25
+ ::Flowbite::Input::Checkbox
26
+ end
27
+
28
+ private
29
+
30
+ def hint_classes
31
+ if disabled?
32
+ "text-xs font-normal text-gray-400 dark:text-gray-500"
33
+ else
34
+ "text-xs font-normal text-gray-500 dark:text-gray-300"
35
+ end
36
+ end
37
+
38
+ def input_arguments
39
+ args = super
40
+ args[:unchecked_value] = @input[:unchecked_value] if @input.key?(:unchecked_value)
41
+ args[:value] = @input[:value] if @input.key?(:value)
42
+ args
43
+ end
44
+
45
+ def label_classes
46
+ if disabled?
47
+ "font-medium text-gray-400 dark:text-gray-500"
48
+ else
49
+ "font-medium text-gray-900 dark:text-gray-300"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Date < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Date
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 DateTime < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::DateTime
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 Email < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Email
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 File < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::File
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ <div>
2
+ <%= label %>
3
+ <%= input %>
4
+ <%= hint %>
5
+ <% errors.each do |error| %>
6
+ <%= render(Flowbite::Input::ValidationError.new) { error.upcase_first } %>
7
+ <% end %>
8
+ </div>
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flowbite
4
+ class InputField
5
+ class Number < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Number
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 Password < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Password
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 Phone < InputField
6
+ protected
7
+
8
+ def input_component
9
+ ::Flowbite::Input::Phone
10
+ end
11
+ end
12
+ end
13
+ end