hakumi_components 0.1.18.pre → 0.1.19.pre

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 (467) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -2
  3. data/README.md +208 -381
  4. data/app/assets/javascripts/hakumi_components.js +40 -34
  5. data/app/assets/stylesheets/hakumi_components.css +1 -1
  6. data/app/components/hakumi_components/admin_panel/component.rb +19 -9
  7. data/app/components/hakumi_components/affix/component.rb +54 -30
  8. data/app/components/hakumi_components/alert/component.html.erb +2 -2
  9. data/app/components/hakumi_components/alert/component.rb +68 -18
  10. data/app/components/hakumi_components/anchor/component.rb +35 -27
  11. data/app/components/hakumi_components/anchor/link/component.html.erb +1 -1
  12. data/app/components/hakumi_components/anchor/link/component.rb +28 -9
  13. data/app/components/hakumi_components/autocomplete/component.html.erb +86 -66
  14. data/app/components/hakumi_components/autocomplete/component.rb +115 -89
  15. data/app/components/hakumi_components/avatar/component.rb +45 -12
  16. data/app/components/hakumi_components/badge/component.rb +99 -45
  17. data/app/components/hakumi_components/base_component.rb +290 -91
  18. data/app/components/hakumi_components/breadcrumb/component.rb +16 -5
  19. data/app/components/hakumi_components/breadcrumb/item/component.html.erb +5 -5
  20. data/app/components/hakumi_components/breadcrumb/item/component.rb +56 -8
  21. data/app/components/hakumi_components/breadcrumb/item/menu_entry.rb +71 -0
  22. data/app/components/hakumi_components/button/component.rb +62 -20
  23. data/app/components/hakumi_components/calendar/component.html.erb +19 -19
  24. data/app/components/hakumi_components/calendar/component.rb +225 -202
  25. data/app/components/hakumi_components/calendar/day_cell.rb +62 -0
  26. data/app/components/hakumi_components/calendar/display_model.rb +237 -0
  27. data/app/components/hakumi_components/calendar/event_entry.rb +54 -0
  28. data/app/components/hakumi_components/calendar/locale_pack.rb +28 -0
  29. data/app/components/hakumi_components/calendar/panel_entry.rb +48 -0
  30. data/app/components/hakumi_components/calendar/week_row.rb +28 -0
  31. data/app/components/hakumi_components/card/component.html.erb +2 -2
  32. data/app/components/hakumi_components/card/component.rb +76 -49
  33. data/app/components/hakumi_components/card/grid/component.html.erb +1 -1
  34. data/app/components/hakumi_components/card/grid/component.rb +23 -17
  35. data/app/components/hakumi_components/card/meta/component.html.erb +8 -8
  36. data/app/components/hakumi_components/card/meta/component.rb +42 -24
  37. data/app/components/hakumi_components/carousel/autoplay_config.rb +27 -0
  38. data/app/components/hakumi_components/carousel/component.rb +85 -67
  39. data/app/components/hakumi_components/carousel/dots_config.rb +36 -0
  40. data/app/components/hakumi_components/cascader/component.html.erb +54 -36
  41. data/app/components/hakumi_components/cascader/component.rb +111 -62
  42. data/app/components/hakumi_components/checkbox/component.html.erb +5 -5
  43. data/app/components/hakumi_components/checkbox/component.rb +40 -6
  44. data/app/components/hakumi_components/checkbox/group/component.html.erb +4 -4
  45. data/app/components/hakumi_components/checkbox/group/component.rb +40 -13
  46. data/app/components/hakumi_components/checkbox/group/option.rb +34 -0
  47. data/app/components/hakumi_components/collapse/component.rb +64 -48
  48. data/app/components/hakumi_components/collapse/panel/component.rb +64 -21
  49. data/app/components/hakumi_components/color_picker/component.html.erb +21 -22
  50. data/app/components/hakumi_components/color_picker/component.rb +159 -49
  51. data/app/components/hakumi_components/color_picker/preset_group.rb +27 -0
  52. data/app/components/hakumi_components/concerns/form_field.rb +169 -68
  53. data/app/components/hakumi_components/concerns/form_field_contract.rb +57 -0
  54. data/app/components/hakumi_components/concerns/form_field_interface.rb +16 -0
  55. data/app/components/hakumi_components/concerns/input_control.rb +111 -0
  56. data/app/components/hakumi_components/concerns/input_control_contract.rb +47 -0
  57. data/app/components/hakumi_components/concerns/input_control_interface.rb +34 -0
  58. data/app/components/hakumi_components/concerns/selection_control.rb +122 -0
  59. data/app/components/hakumi_components/concerns/selection_control_contract.rb +47 -0
  60. data/app/components/hakumi_components/concerns/selection_control_interface.rb +34 -0
  61. data/app/components/hakumi_components/container/component.rb +29 -14
  62. data/app/components/hakumi_components/date_picker/component.html.erb +1 -3
  63. data/app/components/hakumi_components/date_picker/component.rb +130 -16
  64. data/app/components/hakumi_components/date_picker/range_picker.rb +159 -28
  65. data/app/components/hakumi_components/date_picker/shared_rendering.rb +151 -61
  66. data/app/components/hakumi_components/descriptions/component.html.erb +6 -6
  67. data/app/components/hakumi_components/descriptions/component.rb +98 -61
  68. data/app/components/hakumi_components/descriptions/item/component.rb +32 -8
  69. data/app/components/hakumi_components/divider/component.rb +36 -16
  70. data/app/components/hakumi_components/drawer/component.html.erb +2 -2
  71. data/app/components/hakumi_components/drawer/component.rb +112 -29
  72. data/app/components/hakumi_components/dropdown/component.rb +32 -35
  73. data/app/components/hakumi_components/dropdown/divider/component.rb +4 -0
  74. data/app/components/hakumi_components/dropdown/item/component.rb +42 -16
  75. data/app/components/hakumi_components/empty/component.rb +66 -31
  76. data/app/components/hakumi_components/flex/component.rb +39 -21
  77. data/app/components/hakumi_components/float_button/back_top/component.rb +35 -28
  78. data/app/components/hakumi_components/float_button/component.rb +161 -75
  79. data/app/components/hakumi_components/float_button/group/component.rb +85 -98
  80. data/app/components/hakumi_components/float_button/group_cluster/component.rb +46 -54
  81. data/app/components/hakumi_components/float_button/group_spec.rb +259 -0
  82. data/app/components/hakumi_components/float_button/item_spec.rb +115 -0
  83. data/app/components/hakumi_components/float_button/position_config.rb +68 -0
  84. data/app/components/hakumi_components/form/item/component.rb +34 -27
  85. data/app/components/hakumi_components/grid/col/component.rb +88 -17
  86. data/app/components/hakumi_components/grid/row/component.rb +35 -17
  87. data/app/components/hakumi_components/icon/component.rb +49 -49
  88. data/app/components/hakumi_components/image/component.html.erb +7 -7
  89. data/app/components/hakumi_components/image/component.rb +103 -40
  90. data/app/components/hakumi_components/image/preview_group/component.html.erb +1 -1
  91. data/app/components/hakumi_components/image/preview_group/component.rb +41 -36
  92. data/app/components/hakumi_components/image/preview_group/item.rb +48 -0
  93. data/app/components/hakumi_components/input/component.html.erb +1 -1
  94. data/app/components/hakumi_components/input/component.rb +142 -66
  95. data/app/components/hakumi_components/input/password/component.rb +30 -41
  96. data/app/components/hakumi_components/input/text_area/component.html.erb +2 -2
  97. data/app/components/hakumi_components/input/text_area/component.rb +89 -42
  98. data/app/components/hakumi_components/input_number/component.html.erb +5 -5
  99. data/app/components/hakumi_components/input_number/component.rb +137 -55
  100. data/app/components/hakumi_components/layout/component.html.erb +1 -1
  101. data/app/components/hakumi_components/layout/component.rb +23 -16
  102. data/app/components/hakumi_components/layout/content/component.html.erb +1 -1
  103. data/app/components/hakumi_components/layout/content/component.rb +24 -14
  104. data/app/components/hakumi_components/layout/footer/component.html.erb +1 -1
  105. data/app/components/hakumi_components/layout/footer/component.rb +24 -14
  106. data/app/components/hakumi_components/layout/header/component.html.erb +1 -1
  107. data/app/components/hakumi_components/layout/header/component.rb +24 -14
  108. data/app/components/hakumi_components/layout/sider/component.html.erb +1 -1
  109. data/app/components/hakumi_components/layout/sider/component.rb +83 -49
  110. data/app/components/hakumi_components/mentions/coercion.rb +46 -0
  111. data/app/components/hakumi_components/mentions/component.html.erb +6 -6
  112. data/app/components/hakumi_components/mentions/component.rb +131 -67
  113. data/app/components/hakumi_components/mentions/option.rb +30 -0
  114. data/app/components/hakumi_components/menu/component.rb +50 -59
  115. data/app/components/hakumi_components/menu/divider/component.rb +4 -0
  116. data/app/components/hakumi_components/menu/group/component.rb +16 -15
  117. data/app/components/hakumi_components/menu/item/component.rb +42 -19
  118. data/app/components/hakumi_components/menu/sub_menu/component.rb +49 -37
  119. data/app/components/hakumi_components/message/component.rb +62 -27
  120. data/app/components/hakumi_components/modal/component.html.erb +6 -6
  121. data/app/components/hakumi_components/modal/component.rb +93 -40
  122. data/app/components/hakumi_components/modal/confirm/component.html.erb +2 -2
  123. data/app/components/hakumi_components/modal/confirm/component.rb +49 -8
  124. data/app/components/hakumi_components/modal/error/component.rb +8 -39
  125. data/app/components/hakumi_components/modal/info/component.rb +6 -40
  126. data/app/components/hakumi_components/modal/status_component.rb +150 -0
  127. data/app/components/hakumi_components/modal/success/component.rb +6 -40
  128. data/app/components/hakumi_components/modal/warning/component.rb +14 -44
  129. data/app/components/hakumi_components/notification/component.rb +72 -38
  130. data/app/components/hakumi_components/pagination/component.html.erb +7 -11
  131. data/app/components/hakumi_components/pagination/component.rb +105 -72
  132. data/app/components/hakumi_components/pagination/page_item.rb +49 -0
  133. data/app/components/hakumi_components/popconfirm/component.rb +67 -19
  134. data/app/components/hakumi_components/popover/component.html.erb +2 -2
  135. data/app/components/hakumi_components/popover/component.rb +102 -38
  136. data/app/components/hakumi_components/progress/attribute_renderer.rb +354 -0
  137. data/app/components/hakumi_components/progress/circle_geometry.rb +76 -0
  138. data/app/components/hakumi_components/progress/component.html.erb +18 -17
  139. data/app/components/hakumi_components/progress/component.rb +230 -402
  140. data/app/components/hakumi_components/progress/controller_locals_parser.rb +130 -0
  141. data/app/components/hakumi_components/progress/info_tooltip_policy.rb +39 -0
  142. data/app/components/hakumi_components/progress/info_value.rb +75 -0
  143. data/app/components/hakumi_components/progress/status_state.rb +31 -0
  144. data/app/components/hakumi_components/progress/steps_renderer.rb +149 -0
  145. data/app/components/hakumi_components/progress/stroke_gradient_value.rb +90 -0
  146. data/app/components/hakumi_components/qr_code/component.rb +138 -72
  147. data/app/components/hakumi_components/radio/component.html.erb +3 -3
  148. data/app/components/hakumi_components/radio/component.rb +42 -9
  149. data/app/components/hakumi_components/radio/group/component.html.erb +9 -9
  150. data/app/components/hakumi_components/radio/group/component.rb +76 -27
  151. data/app/components/hakumi_components/radio/group/option.rb +43 -0
  152. data/app/components/hakumi_components/rate/component.html.erb +1 -1
  153. data/app/components/hakumi_components/rate/component.rb +55 -9
  154. data/app/components/hakumi_components/result/component.rb +87 -37
  155. data/app/components/hakumi_components/segmented/component.html.erb +1 -1
  156. data/app/components/hakumi_components/segmented/component.rb +81 -118
  157. data/app/components/hakumi_components/segmented/option.rb +191 -0
  158. data/app/components/hakumi_components/select/component.html.erb +19 -19
  159. data/app/components/hakumi_components/select/component.rb +88 -37
  160. data/app/components/hakumi_components/selection_control/coercion.rb +161 -0
  161. data/app/components/hakumi_components/selection_control/entry.rb +22 -0
  162. data/app/components/hakumi_components/selection_control/option.rb +42 -0
  163. data/app/components/hakumi_components/selection_control/option_group.rb +34 -0
  164. data/app/components/hakumi_components/selection_control/tree_node.rb +123 -0
  165. data/app/components/hakumi_components/skeleton/avatar/component.rb +36 -14
  166. data/app/components/hakumi_components/skeleton/avatar_config.rb +79 -0
  167. data/app/components/hakumi_components/skeleton/button/component.rb +32 -14
  168. data/app/components/hakumi_components/skeleton/component.rb +79 -93
  169. data/app/components/hakumi_components/skeleton/image/component.rb +46 -22
  170. data/app/components/hakumi_components/skeleton/input/component.rb +25 -10
  171. data/app/components/hakumi_components/skeleton/node/component.rb +23 -7
  172. data/app/components/hakumi_components/skeleton/paragraph_config.rb +92 -0
  173. data/app/components/hakumi_components/skeleton/title_config.rb +31 -0
  174. data/app/components/hakumi_components/slider/component.html.erb +11 -11
  175. data/app/components/hakumi_components/slider/component.rb +172 -53
  176. data/app/components/hakumi_components/slider/mark.rb +27 -0
  177. data/app/components/hakumi_components/space/compact/component.html.erb +1 -3
  178. data/app/components/hakumi_components/space/compact/component.rb +35 -19
  179. data/app/components/hakumi_components/space/component.html.erb +1 -1
  180. data/app/components/hakumi_components/space/component.rb +75 -39
  181. data/app/components/hakumi_components/spin/component.rb +92 -31
  182. data/app/components/hakumi_components/splitter/component.html.erb +9 -3
  183. data/app/components/hakumi_components/splitter/component.rb +55 -22
  184. data/app/components/hakumi_components/splitter/panel/component.html.erb +1 -3
  185. data/app/components/hakumi_components/splitter/panel/component.rb +64 -39
  186. data/app/components/hakumi_components/statistic/component.rb +143 -59
  187. data/app/components/hakumi_components/steps/component.rb +87 -55
  188. data/app/components/hakumi_components/steps/item/component.rb +59 -20
  189. data/app/components/hakumi_components/switch/component.html.erb +2 -3
  190. data/app/components/hakumi_components/switch/component.rb +62 -36
  191. data/app/components/hakumi_components/table/column/component.rb +80 -23
  192. data/app/components/hakumi_components/table/column_definition.rb +181 -0
  193. data/app/components/hakumi_components/table/column_group/component.rb +32 -21
  194. data/app/components/hakumi_components/table/component.html.erb +22 -31
  195. data/app/components/hakumi_components/table/component.rb +248 -509
  196. data/app/components/hakumi_components/table/concerns/attribute_helpers.rb +22 -0
  197. data/app/components/hakumi_components/table/concerns/cell_rendering.rb +277 -0
  198. data/app/components/hakumi_components/table/concerns/columns.rb +293 -91
  199. data/app/components/hakumi_components/table/concerns/editable.rb +36 -89
  200. data/app/components/hakumi_components/table/concerns/ellipsis.rb +31 -30
  201. data/app/components/hakumi_components/table/concerns/fixed_columns.rb +26 -19
  202. data/app/components/hakumi_components/table/concerns/surface_rendering.rb +365 -0
  203. data/app/components/hakumi_components/table/configs.rb +518 -0
  204. data/app/components/hakumi_components/table/definition_types.rb +40 -0
  205. data/app/components/hakumi_components/table/display_rows.rb +56 -0
  206. data/app/components/hakumi_components/table/ellipsis_config.rb +143 -0
  207. data/app/components/hakumi_components/table/fixed_offset.rb +36 -0
  208. data/app/components/hakumi_components/table/header_cell.rb +23 -0
  209. data/app/components/hakumi_components/table/row_record.rb +60 -0
  210. data/app/components/hakumi_components/table/row_render_state.rb +85 -0
  211. data/app/components/hakumi_components/tabs/component.html.erb +8 -8
  212. data/app/components/hakumi_components/tabs/component.rb +114 -64
  213. data/app/components/hakumi_components/tabs/indicator_config.rb +34 -0
  214. data/app/components/hakumi_components/tabs/item/component.rb +73 -13
  215. data/app/components/hakumi_components/tag/component.rb +142 -47
  216. data/app/components/hakumi_components/tag/group/component.rb +18 -6
  217. data/app/components/hakumi_components/time_picker/component.html.erb +8 -8
  218. data/app/components/hakumi_components/time_picker/component.rb +71 -17
  219. data/app/components/hakumi_components/timeline/component.html.erb +3 -2
  220. data/app/components/hakumi_components/timeline/component.rb +188 -55
  221. data/app/components/hakumi_components/timeline/item/component.html.erb +2 -2
  222. data/app/components/hakumi_components/timeline/item/component.rb +121 -60
  223. data/app/components/hakumi_components/tooltip/component.html.erb +1 -1
  224. data/app/components/hakumi_components/tooltip/component.rb +89 -64
  225. data/app/components/hakumi_components/tour/component.rb +74 -49
  226. data/app/components/hakumi_components/tour/step.rb +86 -0
  227. data/app/components/hakumi_components/transfer/coercion.rb +66 -0
  228. data/app/components/hakumi_components/transfer/component.html.erb +171 -140
  229. data/app/components/hakumi_components/transfer/component.rb +232 -82
  230. data/app/components/hakumi_components/transfer/item.rb +54 -0
  231. data/app/components/hakumi_components/transfer/operations.rb +31 -0
  232. data/app/components/hakumi_components/tree/component.rb +219 -400
  233. data/app/components/hakumi_components/tree/concerns/node_rendering.rb +365 -0
  234. data/app/components/hakumi_components/tree/node.rb +276 -0
  235. data/app/components/hakumi_components/tree_select/component.html.erb +23 -9
  236. data/app/components/hakumi_components/tree_select/component.rb +116 -107
  237. data/app/components/hakumi_components/typography/base_component.rb +69 -36
  238. data/app/components/hakumi_components/typography/link/component.rb +63 -6
  239. data/app/components/hakumi_components/typography/paragraph/component.rb +6 -0
  240. data/app/components/hakumi_components/typography/text/component.rb +4 -0
  241. data/app/components/hakumi_components/typography/title/component.rb +61 -6
  242. data/app/components/hakumi_components/upload/component.html.erb +106 -88
  243. data/app/components/hakumi_components/upload/component.rb +222 -48
  244. data/app/components/hakumi_components/upload/file_entry.rb +53 -0
  245. data/app/controllers/hakumi_components/components_controller.rb +2 -1
  246. data/app/form_builders/hakumi_components/form_builder/contracts.rb +55 -0
  247. data/app/form_builders/hakumi_components/form_builder/field_context.rb +43 -0
  248. data/app/form_builders/hakumi_components/form_builder/model_binding.rb +163 -0
  249. data/app/form_builders/hakumi_components/form_builder.rb +238 -317
  250. data/app/helpers/hakumi_components/form_helper.rb +27 -23
  251. data/app/javascript/hakumi_components/controllers/hakumi/admin_panel_controller.js +5 -7
  252. data/app/javascript/hakumi_components/controllers/hakumi/back_top_controller.js +1 -1
  253. data/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +108 -2
  254. data/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +183 -95
  255. data/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +23 -285
  256. data/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +274 -262
  257. data/app/javascript/hakumi_components/controllers/hakumi/float_button_group_controller.js +2 -2
  258. data/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +4 -2
  259. data/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +119 -125
  260. data/app/javascript/hakumi_components/controllers/hakumi/table/editable.js +291 -0
  261. data/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +166 -366
  262. data/app/javascript/hakumi_components/controllers/hakumi/tabs_controller.js +8 -2
  263. data/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +27 -25
  264. data/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +19 -18
  265. data/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +116 -117
  266. data/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +17 -1
  267. data/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +363 -78
  268. data/app/javascript/hakumi_components/controllers/hakumi/typography_controller.js +3 -3
  269. data/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +320 -204
  270. data/app/javascript/hakumi_components/core/render_component.js +37 -11
  271. data/app/javascript/hakumi_components/utils/color_helper.js +262 -0
  272. data/app/javascript/stylesheets/_base.scss +9 -0
  273. data/app/javascript/stylesheets/_hakumi_components.scss +1 -0
  274. data/app/javascript/stylesheets/components/_breadcrumb.scss +2 -2
  275. data/app/javascript/stylesheets/components/_calendar.scss +13 -13
  276. data/app/javascript/stylesheets/components/_cascader.scss +5 -5
  277. data/app/javascript/stylesheets/components/_checkbox.scss +9 -11
  278. data/app/javascript/stylesheets/components/_color_picker.scss +11 -11
  279. data/app/javascript/stylesheets/components/_date_picker.scss +4 -4
  280. data/app/javascript/stylesheets/components/_descriptions.scss +2 -2
  281. data/app/javascript/stylesheets/components/_drawer.scss +3 -3
  282. data/app/javascript/stylesheets/components/_dropdown.scss +2 -2
  283. data/app/javascript/stylesheets/components/_flex.scss +1 -1
  284. data/app/javascript/stylesheets/components/_float_button.scss +5 -5
  285. data/app/javascript/stylesheets/components/_form_item.scss +92 -0
  286. data/app/javascript/stylesheets/components/_image.scss +15 -15
  287. data/app/javascript/stylesheets/components/_input.scss +23 -113
  288. data/app/javascript/stylesheets/components/_layout.scss +27 -26
  289. data/app/javascript/stylesheets/components/_menu.scss +15 -15
  290. data/app/javascript/stylesheets/components/_modal.scss +13 -13
  291. data/app/javascript/stylesheets/components/_notification.scss +3 -3
  292. data/app/javascript/stylesheets/components/_popover.scss +1 -1
  293. data/app/javascript/stylesheets/components/_segmented.scss +3 -3
  294. data/app/javascript/stylesheets/components/_select.scss +6 -6
  295. data/app/javascript/stylesheets/components/_slider.scss +1 -1
  296. data/app/javascript/stylesheets/components/_spin.scss +2 -2
  297. data/app/javascript/stylesheets/components/_steps.scss +10 -10
  298. data/app/javascript/stylesheets/components/_switch.scss +11 -10
  299. data/app/javascript/stylesheets/components/_table.scss +6 -6
  300. data/app/javascript/stylesheets/components/_tag.scss +2 -2
  301. data/app/javascript/stylesheets/components/_tooltip.scss +4 -4
  302. data/app/javascript/stylesheets/components/_tree_select.scss +3 -3
  303. data/app/javascript/stylesheets/components/_typography.scss +3 -3
  304. data/app/javascript/stylesheets/components/_upload.scss +4 -4
  305. data/app/services/hakumi_components/component_handler.rb +95 -24
  306. data/app/services/hakumi_components/icon/loader.rb +84 -67
  307. data/app/services/hakumi_components/illustrations/loader.rb +35 -27
  308. data/app/views/hakumi/_alert.html.erb +4 -1
  309. data/app/views/hakumi/_drawer.html.erb +1 -1
  310. data/app/views/hakumi/_statistic.html.erb +11 -11
  311. data/lib/generators/hakumi_components/install_generator.rb +14 -1
  312. data/lib/hakumi_components/documentation/models.rb +290 -0
  313. data/lib/hakumi_components/documentation/node.rb +73 -0
  314. data/lib/hakumi_components/documentation.rb +139 -62
  315. data/lib/hakumi_components/engine.rb +17 -11
  316. data/lib/hakumi_components/file_size_checker.rb +215 -0
  317. data/lib/hakumi_components/locales/en.yml +115 -0
  318. data/lib/hakumi_components/rails/attribute_introspection.rb +39 -107
  319. data/lib/hakumi_components/rails/model_reflection.rb +154 -0
  320. data/lib/hakumi_components/rails/validation_introspection.rb +25 -73
  321. data/lib/hakumi_components/rails/validation_mapper.rb +148 -201
  322. data/lib/hakumi_components/rails.rb +2 -0
  323. data/lib/hakumi_components/stylesheet_ownership_checker.rb +191 -0
  324. data/lib/hakumi_components/types/form_field.rb +24 -0
  325. data/lib/hakumi_components/types/html.rb +28 -0
  326. data/lib/hakumi_components/types/rendering.rb +10 -0
  327. data/lib/hakumi_components/types/stimulus.rb +19 -0
  328. data/lib/hakumi_components/types/validation.rb +22 -0
  329. data/lib/hakumi_components/types.rb +13 -0
  330. data/lib/hakumi_components/version.rb +2 -1
  331. data/lib/hakumi_components.rb +26 -6
  332. data/lib/tasks/coverage.rake +1 -0
  333. metadata +100 -136
  334. data/sig/action_view/tag_builder.rbs +0 -24
  335. data/sig/active_support/concern_patch.rbs +0 -7
  336. data/sig/generators/hakumi_components/install_generator.rbs +0 -11
  337. data/sig/hakumi_components/admin_panel/component.rbs +0 -28
  338. data/sig/hakumi_components/affix/component.rbs +0 -44
  339. data/sig/hakumi_components/alert/component.rbs +0 -64
  340. data/sig/hakumi_components/anchor/component.rbs +0 -45
  341. data/sig/hakumi_components/anchor/link/component.rbs +0 -31
  342. data/sig/hakumi_components/autocomplete/component.rbs +0 -68
  343. data/sig/hakumi_components/avatar/component.rbs +0 -44
  344. data/sig/hakumi_components/badge/component.rbs +0 -41
  345. data/sig/hakumi_components/base_component.rbs +0 -82
  346. data/sig/hakumi_components/breadcrumb/component.rbs +0 -21
  347. data/sig/hakumi_components/breadcrumb/item/component.rbs +0 -36
  348. data/sig/hakumi_components/button/component.rbs +0 -56
  349. data/sig/hakumi_components/calendar/component.rbs +0 -41
  350. data/sig/hakumi_components/card/component.rbs +0 -50
  351. data/sig/hakumi_components/card/grid/component.rbs +0 -21
  352. data/sig/hakumi_components/card/meta/component.rbs +0 -25
  353. data/sig/hakumi_components/carousel/component.rbs +0 -88
  354. data/sig/hakumi_components/cascader/component.rbs +0 -52
  355. data/sig/hakumi_components/checkbox/component.rbs +0 -46
  356. data/sig/hakumi_components/checkbox/group/component.rbs +0 -34
  357. data/sig/hakumi_components/collapse/component.rbs +0 -57
  358. data/sig/hakumi_components/collapse/panel/component.rbs +0 -47
  359. data/sig/hakumi_components/color_picker/component.rbs +0 -80
  360. data/sig/hakumi_components/concerns/form_field.rbs +0 -89
  361. data/sig/hakumi_components/container/component.rbs +0 -37
  362. data/sig/hakumi_components/date_picker/component.rbs +0 -72
  363. data/sig/hakumi_components/date_picker/range_picker.rbs +0 -68
  364. data/sig/hakumi_components/date_picker/shared_rendering.rbs +0 -41
  365. data/sig/hakumi_components/descriptions/component.rbs +0 -59
  366. data/sig/hakumi_components/descriptions/item/component.rbs +0 -31
  367. data/sig/hakumi_components/divider/component.rbs +0 -39
  368. data/sig/hakumi_components/drawer/component.rbs +0 -66
  369. data/sig/hakumi_components/dropdown/component.rbs +0 -45
  370. data/sig/hakumi_components/dropdown/divider/component.rbs +0 -11
  371. data/sig/hakumi_components/dropdown/item/component.rbs +0 -45
  372. data/sig/hakumi_components/empty/component.rbs +0 -40
  373. data/sig/hakumi_components/flex/component.rbs +0 -47
  374. data/sig/hakumi_components/float_button/back_top/component.rbs +0 -45
  375. data/sig/hakumi_components/float_button/component.rbs +0 -80
  376. data/sig/hakumi_components/float_button/group/component.rbs +0 -95
  377. data/sig/hakumi_components/float_button/group_cluster/component.rbs +0 -60
  378. data/sig/hakumi_components/form/item/component.rbs +0 -39
  379. data/sig/hakumi_components/form_builder.rbs +0 -37
  380. data/sig/hakumi_components/grid/col/component.rbs +0 -50
  381. data/sig/hakumi_components/grid/row/component.rbs +0 -46
  382. data/sig/hakumi_components/icon/component.rbs +0 -56
  383. data/sig/hakumi_components/image/component.rbs +0 -56
  384. data/sig/hakumi_components/image/preview_group/component.rbs +0 -28
  385. data/sig/hakumi_components/input/component.rbs +0 -71
  386. data/sig/hakumi_components/input/password/component.rbs +0 -27
  387. data/sig/hakumi_components/input/text_area/component.rbs +0 -42
  388. data/sig/hakumi_components/input_number/component.rbs +0 -85
  389. data/sig/hakumi_components/layout/component.rbs +0 -21
  390. data/sig/hakumi_components/layout/content/component.rbs +0 -19
  391. data/sig/hakumi_components/layout/footer/component.rbs +0 -19
  392. data/sig/hakumi_components/layout/header/component.rbs +0 -19
  393. data/sig/hakumi_components/layout/sider/component.rbs +0 -19
  394. data/sig/hakumi_components/mentions/component.rbs +0 -75
  395. data/sig/hakumi_components/menu/component.rbs +0 -52
  396. data/sig/hakumi_components/menu/divider/component.rbs +0 -11
  397. data/sig/hakumi_components/menu/group/component.rbs +0 -20
  398. data/sig/hakumi_components/menu/item/component.rbs +0 -42
  399. data/sig/hakumi_components/menu/sub_menu/component.rbs +0 -37
  400. data/sig/hakumi_components/message/component.rbs +0 -50
  401. data/sig/hakumi_components/modal/component.rbs +0 -76
  402. data/sig/hakumi_components/modal/confirm/component.rbs +0 -32
  403. data/sig/hakumi_components/modal/error/component.rbs +0 -27
  404. data/sig/hakumi_components/modal/info/component.rbs +0 -27
  405. data/sig/hakumi_components/modal/success/component.rbs +0 -27
  406. data/sig/hakumi_components/modal/warning/component.rbs +0 -27
  407. data/sig/hakumi_components/notification/component.rbs +0 -64
  408. data/sig/hakumi_components/pagination/component.rbs +0 -113
  409. data/sig/hakumi_components/popconfirm/component.rbs +0 -52
  410. data/sig/hakumi_components/popover/component.rbs +0 -66
  411. data/sig/hakumi_components/progress/component.rbs +0 -201
  412. data/sig/hakumi_components/qr_code/component.rbs +0 -111
  413. data/sig/hakumi_components/radio/component.rbs +0 -55
  414. data/sig/hakumi_components/radio/group/component.rbs +0 -71
  415. data/sig/hakumi_components/rails/attribute_introspection.rbs +0 -16
  416. data/sig/hakumi_components/rails/validation_introspection.rbs +0 -18
  417. data/sig/hakumi_components/rails/validation_mapper.rbs +0 -53
  418. data/sig/hakumi_components/rails.rbs +0 -6
  419. data/sig/hakumi_components/rate/component.rbs +0 -56
  420. data/sig/hakumi_components/result/component.rbs +0 -65
  421. data/sig/hakumi_components/segmented/component.rbs +0 -72
  422. data/sig/hakumi_components/select/component.rbs +0 -73
  423. data/sig/hakumi_components/skeleton/avatar/component.rbs +0 -23
  424. data/sig/hakumi_components/skeleton/button/component.rbs +0 -24
  425. data/sig/hakumi_components/skeleton/component.rbs +0 -76
  426. data/sig/hakumi_components/skeleton/image/component.rbs +0 -22
  427. data/sig/hakumi_components/skeleton/input/component.rbs +0 -23
  428. data/sig/hakumi_components/skeleton/node/component.rbs +0 -21
  429. data/sig/hakumi_components/slider/component.rbs +0 -85
  430. data/sig/hakumi_components/space/compact/component.rbs +0 -21
  431. data/sig/hakumi_components/space/component.rbs +0 -47
  432. data/sig/hakumi_components/spin/component.rbs +0 -77
  433. data/sig/hakumi_components/splitter/component.rbs +0 -27
  434. data/sig/hakumi_components/splitter/panel/component.rbs +0 -36
  435. data/sig/hakumi_components/statistic/component.rbs +0 -97
  436. data/sig/hakumi_components/steps/component.rbs +0 -82
  437. data/sig/hakumi_components/steps/item/component.rbs +0 -29
  438. data/sig/hakumi_components/switch/component.rbs +0 -52
  439. data/sig/hakumi_components/table/column/component.rbs +0 -57
  440. data/sig/hakumi_components/table/column_group/component.rbs +0 -20
  441. data/sig/hakumi_components/table/component.rbs +0 -279
  442. data/sig/hakumi_components/table/concerns/columns.rbs +0 -95
  443. data/sig/hakumi_components/table/concerns/editable.rbs +0 -40
  444. data/sig/hakumi_components/table/concerns/ellipsis.rbs +0 -27
  445. data/sig/hakumi_components/table/concerns/fixed_columns.rbs +0 -33
  446. data/sig/hakumi_components/tabs/component.rbs +0 -66
  447. data/sig/hakumi_components/tabs/item/component.rbs +0 -29
  448. data/sig/hakumi_components/tag/component.rbs +0 -86
  449. data/sig/hakumi_components/tag/group/component.rbs +0 -24
  450. data/sig/hakumi_components/time_picker/component.rbs +0 -72
  451. data/sig/hakumi_components/timeline/component.rbs +0 -61
  452. data/sig/hakumi_components/timeline/item/component.rbs +0 -70
  453. data/sig/hakumi_components/tooltip/component.rbs +0 -73
  454. data/sig/hakumi_components/tour/component.rbs +0 -84
  455. data/sig/hakumi_components/transfer/component.rbs +0 -75
  456. data/sig/hakumi_components/tree/component.rbs +0 -126
  457. data/sig/hakumi_components/tree_select/component.rbs +0 -122
  458. data/sig/hakumi_components/typography/base_component.rbs +0 -57
  459. data/sig/hakumi_components/typography/link/component.rbs +0 -28
  460. data/sig/hakumi_components/typography/paragraph/component.rbs +0 -13
  461. data/sig/hakumi_components/typography/text/component.rbs +0 -10
  462. data/sig/hakumi_components/typography/title/component.rbs +0 -28
  463. data/sig/hakumi_components/upload/component.rbs +0 -78
  464. data/sig/hakumi_components.rbs +0 -96
  465. data/sig/rails/active_model/validations/comparison_validator.rbs +0 -6
  466. data/sig/rails.rbs +0 -18
  467. data/sig/view_component/base.rbs +0 -28
@@ -1,22 +1,25 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  require "pathname"
4
5
 
5
6
  module HakumiComponents
6
7
  class Engine < ::Rails::Engine
8
+ extend T::Sig
9
+
7
10
  isolate_namespace HakumiComponents
8
11
 
9
- root = Pathname.new(File.expand_path("../..", __dir__ || "."))
10
- config.root = root
12
+ ROOT = T.let(Pathname.new(File.expand_path("../..", __dir__ || ".")), Pathname)
13
+ config.root = ROOT
11
14
 
12
- config.autoload_paths << root.join("app/components")
13
- config.eager_load_paths << root.join("app/components")
15
+ config.autoload_paths << ROOT.join("app/components")
16
+ config.eager_load_paths << ROOT.join("app/components")
14
17
 
15
- config.autoload_paths << root.join("app/form_builders")
16
- config.eager_load_paths << root.join("app/form_builders")
18
+ config.autoload_paths << ROOT.join("app/form_builders")
19
+ config.eager_load_paths << ROOT.join("app/form_builders")
17
20
 
18
- config.autoload_paths << root.join("app/helpers")
19
- config.eager_load_paths << root.join("app/helpers")
21
+ config.autoload_paths << ROOT.join("app/helpers")
22
+ config.eager_load_paths << ROOT.join("app/helpers")
20
23
 
21
24
  initializer "hakumi.helpers" do
22
25
  ActiveSupport.on_load(:action_view) do
@@ -25,14 +28,17 @@ module HakumiComponents
25
28
  end
26
29
 
27
30
  initializer "hakumi.assets" do |app|
28
- # Only add assets path if Sprockets is available (Rails < 8 or when explicitly added)
29
31
  if app.config.respond_to?(:assets)
30
- app.config.assets.paths << root.join("app/assets")
32
+ app.config.assets.paths << ROOT.join("app/assets")
31
33
  end
32
34
  end
33
35
 
34
36
  initializer "hakumi.views" do |app|
35
- app.config.paths["app/views"] << root.join("app/views")
37
+ app.config.paths["app/views"] << ROOT.join("app/views")
38
+ end
39
+
40
+ initializer "hakumi.i18n" do
41
+ config.i18n.load_path += Dir[ROOT.join("lib/hakumi_components/locales/**/*.yml")]
36
42
  end
37
43
 
38
44
  initializer "hakumi.form_builder", after: :load_config_initializers do |app|
@@ -0,0 +1,215 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+ require "sorbet-runtime"
6
+ require "yaml"
7
+
8
+ module HakumiComponents
9
+ class FileSizeChecker
10
+ extend T::Sig
11
+
12
+ class Rule < T::Struct
13
+ const :name, String
14
+ const :pattern, String
15
+ const :threshold, Integer
16
+ end
17
+
18
+ class Finding < T::Struct
19
+ extend T::Sig
20
+
21
+ const :rule_name, String
22
+ const :path, String
23
+ const :lines, Integer
24
+ const :threshold, Integer
25
+ const :approved_limit, T.nilable(Integer)
26
+
27
+ sig { returns(T::Boolean) }
28
+ def approved?
29
+ limit = approved_limit
30
+ !limit.nil? && lines <= limit
31
+ end
32
+
33
+ sig { returns(T::Boolean) }
34
+ def violation?
35
+ !approved?
36
+ end
37
+
38
+ sig { returns(Integer) }
39
+ def over_threshold_by
40
+ lines - threshold
41
+ end
42
+
43
+ sig { returns(T.nilable(Integer)) }
44
+ def over_approved_by
45
+ limit = approved_limit
46
+ return nil if limit.nil?
47
+
48
+ lines - limit
49
+ end
50
+
51
+ sig { returns(String) }
52
+ def status
53
+ return "approved" if approved?
54
+ return "regressed" unless approved_limit.nil?
55
+
56
+ "unapproved"
57
+ end
58
+ end
59
+
60
+ REPO_ROOT = T.let(Pathname.new(__dir__).join("../..").expand_path, Pathname)
61
+
62
+ DEFAULT_RULES = T.let([
63
+ Rule.new(
64
+ name: "Ruby component",
65
+ pattern: "app/components/hakumi_components/**/component.rb",
66
+ threshold: 500
67
+ ),
68
+ Rule.new(
69
+ name: "Hakumi Stimulus controller",
70
+ pattern: "app/javascript/hakumi_components/controllers/hakumi/*_controller.js",
71
+ threshold: 700
72
+ )
73
+ ].freeze, T::Array[Rule])
74
+
75
+ EXCEPTIONS_PATH = T.let(Pathname.new("docs/file_size_exceptions.yml"), Pathname)
76
+
77
+ sig { params(root: Pathname).returns(T::Hash[String, Integer]) }
78
+ def self.load_approved_exceptions(root: REPO_ROOT)
79
+ path = root.join(EXCEPTIONS_PATH)
80
+ return {} unless path.exist?
81
+
82
+ data = YAML.safe_load(path.read, aliases: false) || {}
83
+ exceptions = T.let({}, T::Hash[String, Integer])
84
+
85
+ T.cast(data, T::Hash[String, T.untyped]).each_value do |entries|
86
+ next unless entries.is_a?(Hash)
87
+
88
+ entries.each do |relative_path, config|
89
+ next unless config.is_a?(Hash)
90
+
91
+ baseline = config["baseline_lines"]
92
+ next if baseline.nil?
93
+
94
+ exceptions[relative_path.to_s] = Integer(baseline)
95
+ end
96
+ end
97
+
98
+ exceptions
99
+ end
100
+
101
+ sig do
102
+ params(
103
+ root: Pathname,
104
+ rules: T::Array[Rule],
105
+ approved_exceptions: T.nilable(T::Hash[String, Integer])
106
+ ).void
107
+ end
108
+ def initialize(root: REPO_ROOT, rules: DEFAULT_RULES, approved_exceptions: nil)
109
+ @root = T.let(root, Pathname)
110
+ @rules = T.let(rules, T::Array[Rule])
111
+ @approved_exceptions = T.let(approved_exceptions || self.class.load_approved_exceptions(root: root),
112
+ T::Hash[String, Integer])
113
+ end
114
+
115
+ sig { returns(T::Array[Finding]) }
116
+ def findings
117
+ @rules.flat_map do |rule|
118
+ matching_paths(rule).filter_map do |path|
119
+ lines = line_count(path)
120
+ next if lines <= rule.threshold
121
+
122
+ relative_path = relative_path_for(path)
123
+ Finding.new(
124
+ rule_name: rule.name,
125
+ path: relative_path,
126
+ lines: lines,
127
+ threshold: rule.threshold,
128
+ approved_limit: @approved_exceptions[relative_path]
129
+ )
130
+ end
131
+ end.sort_by { |finding| [ finding.rule_name, finding.path ] }
132
+ end
133
+
134
+ sig { returns(T::Array[Finding]) }
135
+ def violations
136
+ findings.select(&:violation?)
137
+ end
138
+
139
+ sig { params(strict: T::Boolean).returns(T::Boolean) }
140
+ def success?(strict: false)
141
+ !strict || violations.empty?
142
+ end
143
+
144
+ sig { params(strict: T::Boolean).returns(String) }
145
+ def report(strict: false)
146
+ current_findings = findings
147
+
148
+ lines = []
149
+ lines << "File size guardrail#{strict ? ' (strict)' : ' (warning-only)'}"
150
+ lines << ""
151
+ lines << "Thresholds:"
152
+ @rules.each do |rule|
153
+ lines << "- #{rule.name}: #{rule.threshold} lines (#{rule.pattern})"
154
+ end
155
+ lines << ""
156
+
157
+ if current_findings.empty?
158
+ lines << "All tracked files are within their thresholds."
159
+ return lines.join("\n")
160
+ end
161
+
162
+ approved, actionable = current_findings.partition(&:approved?)
163
+
164
+ unless approved.empty?
165
+ lines << "Approved exceptions:"
166
+ approved.each do |finding|
167
+ lines << format_finding(finding)
168
+ end
169
+ lines << ""
170
+ end
171
+
172
+ unless actionable.empty?
173
+ lines << "Actionable overages:"
174
+ actionable.each do |finding|
175
+ lines << format_finding(finding)
176
+ end
177
+ lines << ""
178
+ end
179
+
180
+ unless strict
181
+ lines << "This check is currently non-blocking. Run `bin/check-file-sizes --strict` to fail on actionable overages."
182
+ end
183
+
184
+ lines.join("\n").strip
185
+ end
186
+
187
+ private
188
+
189
+ sig { params(rule: Rule).returns(T::Array[Pathname]) }
190
+ def matching_paths(rule)
191
+ Dir.glob(@root.join(rule.pattern).to_s).map { |path| Pathname.new(path) }.sort
192
+ end
193
+
194
+ sig { params(path: Pathname).returns(Integer) }
195
+ def line_count(path)
196
+ count = T.let(0, Integer)
197
+ path.each_line { count += 1 }
198
+ count
199
+ end
200
+
201
+ sig { params(path: Pathname).returns(String) }
202
+ def relative_path_for(path)
203
+ path.relative_path_from(@root).to_s
204
+ end
205
+
206
+ sig { params(finding: Finding).returns(String) }
207
+ def format_finding(finding)
208
+ base = "- #{finding.path}: #{finding.lines} lines (limit #{finding.threshold}, +#{finding.over_threshold_by})"
209
+ approved_limit = finding.approved_limit
210
+ return "#{base}; approved exception up to #{approved_limit}" unless approved_limit.nil?
211
+
212
+ "#{base}; no approved exception"
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,115 @@
1
+ en:
2
+ hakumi_components:
3
+ # Modal variants
4
+ modal:
5
+ info:
6
+ title: "Info"
7
+ ok: "OK"
8
+ confirm:
9
+ title: "Confirm"
10
+ ok: "OK"
11
+ cancel: "Cancel"
12
+ error:
13
+ title: "Error"
14
+ ok: "OK"
15
+ warning:
16
+ title: "Warning"
17
+ ok: "Proceed"
18
+ cancel: "Cancel"
19
+ success:
20
+ title: "Success"
21
+ ok: "OK"
22
+
23
+ # Image component
24
+ image:
25
+ preview: "Preview"
26
+ flip_vertical: "Flip Vertical"
27
+ flip_horizontal: "Flip Horizontal"
28
+ rotate_left: "Rotate Left"
29
+ rotate_right: "Rotate Right"
30
+ zoom_out: "Zoom Out"
31
+ zoom_in: "Zoom In"
32
+
33
+ # QR Code component
34
+ qr_code:
35
+ aria_label: "QR Code"
36
+ status:
37
+ expired: "Expired"
38
+ loading: "Loading"
39
+ scanned: "Scanned"
40
+ active: "Active"
41
+ description:
42
+ expired: "Please refresh to continue"
43
+ scanned: "You can now continue"
44
+
45
+ # Typography component
46
+ typography:
47
+ copied: "Copied!"
48
+ copy_failed: "Copy failed"
49
+ copy_aria_label: "Copy"
50
+
51
+ # Transfer component
52
+ transfer:
53
+ source_title: "Source"
54
+ target_title: "Target"
55
+ search_placeholder: "Search"
56
+
57
+ # Autocomplete component
58
+ autocomplete:
59
+ not_found: "No options"
60
+
61
+ # Color Picker component
62
+ color_picker:
63
+ default_preset_label: "Recommended"
64
+ presets_label: "Presets"
65
+
66
+ # Date Picker component
67
+ date_picker:
68
+ placeholder: "Select date"
69
+ start_placeholder: "Start date"
70
+ end_placeholder: "End date"
71
+
72
+ # Pagination component
73
+ pagination:
74
+ total: "Total %{count} items"
75
+
76
+ # Table component
77
+ table:
78
+ select_all: "Select all"
79
+ invert: "Invert"
80
+ clear: "Clear"
81
+ search_placeholder: "Search"
82
+ reset: "Reset"
83
+
84
+ # Tabs component
85
+ tabs:
86
+ remove_tab_aria_label: "Remove tab"
87
+
88
+ # Empty component
89
+ empty:
90
+ description: "No data"
91
+
92
+ # Cascader component
93
+ cascader:
94
+ placeholder: "Select"
95
+
96
+ # Tree Select component
97
+ tree_select:
98
+ placeholder: "Select"
99
+
100
+ # Tree component
101
+ tree:
102
+ search_placeholder: "Search"
103
+
104
+ # Message component
105
+ message:
106
+ default_content: "Generated message content."
107
+ close: "Close"
108
+
109
+ # Upload component
110
+ upload:
111
+ preview: "Preview"
112
+ remove: "Remove"
113
+ close: "Close"
114
+ previous: "Previous"
115
+ next: "Next"
@@ -1,154 +1,86 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Rails
5
- # Introspects ActiveRecord schema and I18n translations to automatically
6
- # configure form field labels, placeholders, and hints
7
6
  class AttributeIntrospection
8
7
  class << self
9
- # Get human-readable attribute name from I18n
10
- #
11
- # @param object [ActiveModel::Model, ActiveRecord::Base]
12
- # @param attribute [Symbol]
13
- # @return [String, nil]
14
- def human_attribute_name(object, attribute)
15
- return nil unless object
16
- return nil unless object.class.respond_to?(:human_attribute_name)
8
+ extend T::Sig
17
9
 
18
- # Use ActiveModel's human_attribute_name which looks up I18n translations
19
- # Falls back to humanized attribute name
20
- object.class.public_send(:human_attribute_name, attribute)
10
+ sig { params(object: T.untyped, attribute: Symbol).returns(T.nilable(String)) }
11
+ def human_attribute_name(object, attribute)
12
+ ModelReflection.for(object).human_attribute_name(attribute)
21
13
  end
22
14
 
23
- # Get placeholder text from I18n
24
- #
25
- # @param object [ActiveModel::Model, ActiveRecord::Base]
26
- # @param attribute [Symbol]
27
- # @return [String, nil]
15
+ sig { params(object: T.untyped, attribute: Symbol).returns(T.nilable(String)) }
28
16
  def placeholder(object, attribute)
29
- return nil unless object
30
17
  return nil unless defined?(I18n)
18
+ model_name = ModelReflection.for(object).model_i18n_key
19
+ return nil unless model_name
31
20
 
32
- model_name = object.class.respond_to?(:model_name) ? object.class.public_send(:model_name) : nil
33
- return nil unless model_name && model_name.respond_to?(:i18n_key)
34
- model_name = model_name.public_send(:i18n_key)
35
21
  key = "helpers.placeholder.#{model_name}.#{attribute}"
36
22
 
37
23
  I18n.t(key, default: "").presence
38
24
  end
39
25
 
40
- # Get hint/help text from I18n
41
- #
42
- # @param object [ActiveModel::Model, ActiveRecord::Base]
43
- # @param attribute [Symbol]
44
- # @return [String, nil]
26
+ sig { params(object: T.untyped, attribute: Symbol).returns(T.nilable(String)) }
45
27
  def hint(object, attribute)
46
- return nil unless object
47
28
  return nil unless defined?(I18n)
29
+ model_name = ModelReflection.for(object).model_i18n_key
30
+ return nil unless model_name
48
31
 
49
- model_name = object.class.respond_to?(:model_name) ? object.class.public_send(:model_name) : nil
50
- return nil unless model_name && model_name.respond_to?(:i18n_key)
51
- model_name = model_name.public_send(:i18n_key)
52
32
  key = "helpers.hint.#{model_name}.#{attribute}"
53
33
 
54
34
  I18n.t(key, default: "").presence
55
35
  end
56
36
 
57
- # Detect HTML input type from database column type
58
- #
59
- # @param object [ActiveRecord::Base]
60
- # @param attribute [Symbol]
61
- # @return [Symbol] :text, :number, :email, :url, :date, :datetime, :time, etc.
37
+ sig { params(object: T.untyped, attribute: Symbol).returns(Symbol) }
62
38
  def input_type_from_column(object, attribute)
63
- return :text unless object
64
- return :text unless defined?(ActiveRecord::Base)
65
- return :text unless object.is_a?(ActiveRecord::Base) || object.class < ActiveRecord::Base
66
-
67
- column = column_for(object, attribute)
68
- return :text unless column
69
-
70
- column_type = column.respond_to?(:type) ? column.public_send(:type) : nil
71
- case column_type
72
- when :integer, :decimal, :float
73
- :number
74
- when :date
75
- :date
76
- when :datetime, :timestamp
77
- :datetime_local
78
- when :time
79
- :time
80
- when :boolean
81
- :checkbox
82
- when :text
83
- :textarea
84
- else
85
- :text
39
+ column = ModelReflection.for(object).column_for(attribute)
40
+ return :text unless column&.type
41
+
42
+ case column.type
43
+ when :integer, :decimal, :float then :number
44
+ when :date then :date
45
+ when :datetime, :timestamp then :datetime_local
46
+ when :time then :time
47
+ when :boolean then :checkbox
48
+ when :text then :textarea
49
+ else :text
86
50
  end
87
51
  end
88
52
 
89
- # Get database column for attribute
90
- #
91
- # @param object [ActiveRecord::Base]
92
- # @param attribute [Symbol]
93
- # @return [ActiveRecord::ConnectionAdapters::Column, nil]
53
+ sig { params(object: T.untyped, attribute: Symbol).returns(T.nilable(HakumiComponents::Rails::ModelReflection::ColumnInfo)) }
94
54
  def column_for(object, attribute)
95
- return nil unless object
96
- return nil unless defined?(ActiveRecord::Base)
97
- return nil unless object.class < ActiveRecord::Base
98
- return nil unless object.class.respond_to?(:columns_hash)
99
-
100
- columns_hash = object.class.respond_to?(:columns_hash) ? object.class.public_send(:columns_hash) : nil
101
- return columns_hash[attribute.to_s] if columns_hash.is_a?(Hash)
102
- nil
55
+ ModelReflection.for(object).column_for(attribute)
103
56
  end
104
57
 
105
- # Check if column is numeric
106
- #
107
- # @param object [ActiveRecord::Base]
108
- # @param attribute [Symbol]
109
- # @return [Boolean]
58
+ sig { params(object: T.untyped, attribute: Symbol).returns(T::Boolean) }
110
59
  def numeric_column?(object, attribute)
111
60
  column = column_for(object, attribute)
112
- return false unless column
113
-
114
- column_type = column.respond_to?(:type) ? column.public_send(:type) : nil
115
- case column_type
116
- when :integer, :decimal, :float
117
- true
118
- else
119
- false
61
+ return false unless column&.type
62
+
63
+ case column.type
64
+ when :integer, :decimal, :float then true
65
+ else false
120
66
  end
121
67
  end
122
68
 
123
- # Check if column is text/string
124
- #
125
- # @param object [ActiveRecord::Base]
126
- # @param attribute [Symbol]
127
- # @return [Boolean]
69
+ sig { params(object: T.untyped, attribute: Symbol).returns(T::Boolean) }
128
70
  def text_column?(object, attribute)
129
71
  column = column_for(object, attribute)
130
- return true unless column # Default to text if no column
131
-
132
- column_type = column.respond_to?(:type) ? column.public_send(:type) : nil
133
- case column_type
134
- when :string, :text
135
- true
136
- else
137
- false
72
+ return true unless column&.type
73
+
74
+ case column.type
75
+ when :string, :text then true
76
+ else false
138
77
  end
139
78
  end
140
79
 
141
- # Get SQL column limit (for string columns)
142
- #
143
- # @param object [ActiveRecord::Base]
144
- # @param attribute [Symbol]
145
- # @return [Integer, nil]
80
+ sig { params(object: T.untyped, attribute: Symbol).returns(T.nilable(Integer)) }
146
81
  def column_limit(object, attribute)
147
82
  column = column_for(object, attribute)
148
- return nil unless column
149
-
150
- return nil unless column.respond_to?(:limit)
151
- column.public_send(:limit)
83
+ column&.limit
152
84
  end
153
85
  end
154
86
  end