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,25 +1,22 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Form
5
6
  module Item
6
- # Hakumi Form Item component
7
- # Wrapper component for form fields that handles labels, errors, and help text
8
- # This is the central component that all form inputs should use or delegate to
9
- #
10
- # @example Basic usage
11
- # <%= render HakumiComponents::Form::Item::Component.new(label: "Email") do %>
12
- # <%= render HakumiComponents::Input::Component.new(name: "email", standalone: true) %>
13
- # <% end %>
14
- #
15
- # @example With error
16
- # <%= render HakumiComponents::Form::Item::Component.new(
17
- # label: "Email",
18
- # errors: ["can't be blank"]
19
- # ) do %>
20
- # <%= render HakumiComponents::Input::Component.new(name: "email", standalone: true) %>
21
- # <% end %>
22
7
  class Component < HakumiComponents::BaseComponent
8
+ extend T::Sig
9
+
10
+ sig do
11
+ params(
12
+ label: T.nilable(String),
13
+ caption: T.nilable(String),
14
+ errors: T.any(String, T::Array[String]),
15
+ required: T::Boolean,
16
+ field_id: T.nilable(String),
17
+ html_options: Types::HtmlAttributeValue
18
+ ).void
19
+ end
23
20
  def initialize(
24
21
  label: nil,
25
22
  caption: nil,
@@ -28,28 +25,31 @@ module HakumiComponents
28
25
  field_id: nil,
29
26
  **html_options
30
27
  )
31
- @label = label
32
- @caption = caption
33
- @errors = Array(errors)
34
- @required = required
35
- @field_id = field_id
36
- @html_options = html_options
28
+ @label = T.let(label, T.nilable(String))
29
+ @caption = T.let(caption, T.nilable(String))
30
+ @errors = T.let(Array(errors), T::Array[String])
31
+ @required = T.let(required, T::Boolean)
32
+ @field_id = T.let(field_id, T.nilable(String))
33
+ @html_options = T.let(html_options, Types::HtmlAttributes)
37
34
  end
38
35
 
36
+ sig { returns(ActiveSupport::SafeBuffer) }
39
37
  def call
40
38
  content_tag(:div, wrapper_content, wrapper_attributes)
41
39
  end
42
40
 
43
41
  private
44
42
 
45
- def has_error?
43
+ sig { returns(T::Boolean) }
44
+ def error?
46
45
  @errors.any?
47
46
  end
48
47
 
48
+ sig { returns(Types::HtmlAttributes) }
49
49
  def wrapper_attributes
50
50
  classes = [ "hakumi-form-item" ]
51
- classes << "hakumi-form-item-has-error" if has_error?
52
- classes << "hakumi-form-item-with-help" if has_error? || @caption
51
+ classes << "hakumi-form-item-has-error" if error?
52
+ classes << "hakumi-form-item-with-help" if error? || @caption
53
53
 
54
54
  merge_attributes(
55
55
  { class: classes.join(" ") },
@@ -57,6 +57,7 @@ module HakumiComponents
57
57
  )
58
58
  end
59
59
 
60
+ sig { returns(ActiveSupport::SafeBuffer) }
60
61
  def wrapper_content
61
62
  safe_join([
62
63
  label_content,
@@ -64,6 +65,7 @@ module HakumiComponents
64
65
  ].compact)
65
66
  end
66
67
 
68
+ sig { returns(T.nilable(ActiveSupport::SafeBuffer)) }
67
69
  def label_content
68
70
  return nil if @label.blank?
69
71
 
@@ -77,6 +79,7 @@ module HakumiComponents
77
79
  end
78
80
  end
79
81
 
82
+ sig { returns(ActiveSupport::SafeBuffer) }
80
83
  def control_content
81
84
  content_tag(:div, class: "hakumi-form-item-control") do
82
85
  safe_join([
@@ -86,18 +89,21 @@ module HakumiComponents
86
89
  end
87
90
  end
88
91
 
92
+ sig { returns(ActiveSupport::SafeBuffer) }
89
93
  def control_input_content
90
94
  content_tag(:div, content, class: "hakumi-form-item-control-input")
91
95
  end
92
96
 
97
+ sig { returns(T.nilable(ActiveSupport::SafeBuffer)) }
93
98
  def explain_content
94
- return nil unless @caption || has_error?
99
+ return nil unless @caption || error?
95
100
 
96
101
  content_tag(:div, class: "hakumi-form-item-explain") do
97
- has_error? ? error_content : caption_content
102
+ error? ? error_content : caption_content
98
103
  end
99
104
  end
100
105
 
106
+ sig { returns(ActiveSupport::SafeBuffer) }
101
107
  def error_content
102
108
  content_tag(:div, @errors.first,
103
109
  class: "hakumi-form-item-explain-error",
@@ -105,6 +111,7 @@ module HakumiComponents
105
111
  )
106
112
  end
107
113
 
114
+ sig { returns(ActiveSupport::SafeBuffer) }
108
115
  def caption_content
109
116
  content_tag(:div, @caption, class: "hakumi-form-item-extra")
110
117
  end
@@ -1,9 +1,36 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Grid
5
6
  module Col
6
7
  class Component < HakumiComponents::BaseComponent
8
+ extend T::Sig
9
+
10
+ GRID_RANGE = T.let((0..24).to_a.freeze, T::Array[Integer])
11
+ RESPONSIVE_PROPS = T.let(%i[span offset order pull push].freeze, T::Array[Symbol])
12
+ BreakpointSettings = T.type_alias { T::Hash[Symbol, Integer] }
13
+ BreakpointValue = T.type_alias { T.any(Integer, BreakpointSettings) }
14
+ BreakpointInputs = T.type_alias { T::Hash[Symbol, T.nilable(BreakpointValue)] }
15
+
16
+ sig do
17
+ params(
18
+ span: T.nilable(Integer),
19
+ offset: T.nilable(Integer),
20
+ order: T.nilable(Integer),
21
+ pull: T.nilable(Integer),
22
+ push: T.nilable(Integer),
23
+ flex: BaseComponent::DimensionInput,
24
+ xs: T.nilable(BreakpointValue),
25
+ sm: T.nilable(BreakpointValue),
26
+ md: T.nilable(BreakpointValue),
27
+ lg: T.nilable(BreakpointValue),
28
+ xl: T.nilable(BreakpointValue),
29
+ xxl: T.nilable(BreakpointValue),
30
+ component: Symbol,
31
+ html_options: HakumiComponents::Types::HtmlAttributeValue
32
+ ).void
33
+ end
7
34
  def initialize(
8
35
  span: nil,
9
36
  offset: nil,
@@ -20,23 +47,66 @@ module HakumiComponents
20
47
  component: :div,
21
48
  **html_options
22
49
  )
23
- @span = span
24
- @offset = offset
25
- @order = order
26
- @pull = pull
27
- @push = push
28
- @flex = flex
29
- @breakpoints = { xs: xs, sm: sm, md: md, lg: lg, xl: xl, xxl: xxl }
30
- @component = component
31
- @html_options = html_options
50
+ @span = T.let(span, T.nilable(Integer))
51
+ @offset = T.let(offset, T.nilable(Integer))
52
+ @order = T.let(order, T.nilable(Integer))
53
+ @pull = T.let(pull, T.nilable(Integer))
54
+ @push = T.let(push, T.nilable(Integer))
55
+ @flex = T.let(flex, BaseComponent::DimensionInput)
56
+ @breakpoints = T.let(
57
+ { xs: xs, sm: sm, md: md, lg: lg, xl: xl, xxl: xxl },
58
+ BreakpointInputs
59
+ )
60
+ @component = T.let(component, Symbol)
61
+ @html_options = T.let(html_options, HakumiComponents::Types::HtmlAttributes)
62
+
63
+ validate_props!
32
64
  end
33
65
 
66
+ sig { returns(ActiveSupport::SafeBuffer) }
34
67
  def call
35
68
  content_tag(@component, content, wrapper_attributes)
36
69
  end
37
70
 
38
71
  private
39
72
 
73
+ sig { void }
74
+ def validate_props!
75
+ validate_grid_value!(:span, @span)
76
+ validate_grid_value!(:offset, @offset)
77
+ validate_grid_value!(:order, @order)
78
+ validate_grid_value!(:pull, @pull)
79
+ validate_grid_value!(:push, @push)
80
+ validate_breakpoints!
81
+ end
82
+
83
+ sig { params(prop: Symbol, value: T.nilable(Integer)).void }
84
+ def validate_grid_value!(prop, value)
85
+ return if value.nil?
86
+ return if GRID_RANGE.include?(value)
87
+
88
+ raise ArgumentError, "#{prop} must be between 0 and 24, got #{value.inspect}"
89
+ end
90
+
91
+ sig { void }
92
+ def validate_breakpoints!
93
+ @breakpoints.each do |size, value|
94
+ next if value.nil?
95
+
96
+ if value.is_a?(Integer)
97
+ validate_grid_value!(size, value)
98
+ elsif value.is_a?(Hash)
99
+ value.each do |prop, val|
100
+ unless RESPONSIVE_PROPS.include?(prop)
101
+ raise ArgumentError, "#{size} responsive prop must be one of #{RESPONSIVE_PROPS.inspect}, got #{prop.inspect}"
102
+ end
103
+ validate_grid_value!(:"#{size}.#{prop}", val)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ sig { returns(String) }
40
110
  def wrapper_classes
41
111
  classes = [ "hakumi-col" ]
42
112
 
@@ -51,7 +121,7 @@ module HakumiComponents
51
121
  @breakpoints.each do |size, value|
52
122
  next if value.nil?
53
123
 
54
- if value.is_a?(Numeric)
124
+ if value.is_a?(Integer)
55
125
  classes << "hakumi-col-#{size}-#{value}"
56
126
  elsif value.is_a?(Hash)
57
127
  value.each do |prop, val|
@@ -61,11 +131,13 @@ module HakumiComponents
61
131
  end
62
132
  end
63
133
 
64
- class_names(nil, {}, [ classes, @html_options[:class] ])
134
+ classes.concat(Array(html_classes(@html_options)).compact)
135
+ classes.compact.join(" ")
65
136
  end
66
137
 
138
+ sig { returns(HakumiComponents::Types::HtmlAttributes) }
67
139
  def wrapper_attributes
68
- combined_style = [ @html_options[:style], flex_style ].compact.join(";")
140
+ combined_style = [ html_style(@html_options), flex_style ].compact.join(";")
69
141
  attrs = {
70
142
  class: wrapper_classes
71
143
  }
@@ -74,11 +146,12 @@ module HakumiComponents
74
146
  merge_attributes(attrs, @html_options.except(:class, :style))
75
147
  end
76
148
 
149
+ sig { returns(T.nilable(String)) }
77
150
  def flex_style
78
151
  return nil unless @flex
79
152
 
80
- style = []
81
- if @flex.is_a?(Numeric)
153
+ style = T.let([], T::Array[String])
154
+ if @flex.is_a?(Integer)
82
155
  style << "flex: #{@flex} #{@flex} auto"
83
156
  elsif @flex == "auto"
84
157
  style << "flex: 1 1 auto"
@@ -88,9 +161,7 @@ module HakumiComponents
88
161
  style << "flex: #{@flex}"
89
162
  end
90
163
 
91
- # Merge with existing style
92
- existing_style = @html_options[:style]
93
- [ existing_style, style.join(";") ].compact.join(";")
164
+ style.join(";")
94
165
  end
95
166
  end
96
167
  end
@@ -1,12 +1,25 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Grid
5
6
  module Row
6
7
  class Component < HakumiComponents::BaseComponent
7
- JUSTIFY_OPTIONS = [ :start, :end, :center, :"space-around", :"space-between" ].freeze
8
- ALIGN_OPTIONS = [ :top, :middle, :bottom ].freeze
8
+ extend T::Sig
9
9
 
10
+ JUSTIFY_VALUES = T.let([ :start, :end, :center, :"space-around", :"space-between" ].freeze, T::Array[Symbol])
11
+ ALIGN_VALUES = T.let([ :top, :middle, :bottom ].freeze, T::Array[Symbol])
12
+
13
+ sig do
14
+ params(
15
+ gutter: T.nilable(T.any(Integer, [ Integer, Integer ])),
16
+ justify: Symbol,
17
+ align: Symbol,
18
+ wrap: T::Boolean,
19
+ component: Symbol,
20
+ html_options: HakumiComponents::Types::HtmlAttributeValue
21
+ ).void
22
+ end
10
23
  def initialize(
11
24
  gutter: 0,
12
25
  justify: :start,
@@ -15,34 +28,38 @@ module HakumiComponents
15
28
  component: :div,
16
29
  **html_options
17
30
  )
18
- @gutter = normalize_gutter(gutter)
19
- @justify = justify
20
- @align = align
21
- @wrap = wrap
22
- @component = component
23
- @html_options = html_options
31
+ @gutter = T.let(normalize_gutter(gutter), [ Integer, Integer ])
32
+ @justify = T.let(justify, Symbol)
33
+ @align = T.let(align, Symbol)
34
+ @wrap = T.let(wrap, T::Boolean)
35
+ @component = T.let(component, Symbol)
36
+ @html_options = T.let(html_options, HakumiComponents::Types::HtmlAttributes)
24
37
 
25
38
  validate_props!
26
39
  end
27
40
 
41
+ sig { returns(ActiveSupport::SafeBuffer) }
28
42
  def call
29
43
  content_tag(@component, content, wrapper_attributes)
30
44
  end
31
45
 
32
46
  private
33
47
 
48
+ sig { void }
34
49
  def validate_props!
35
- validate_inclusion!(:justify, JUSTIFY_OPTIONS)
36
- validate_inclusion!(:align, ALIGN_OPTIONS)
50
+ validate_inclusion!(:justify, JUSTIFY_VALUES)
51
+ validate_inclusion!(:align, ALIGN_VALUES)
37
52
  end
38
53
 
54
+ sig { params(gutter: T.nilable(T.any(Integer, [ Integer, Integer ]))).returns([ Integer, Integer ]) }
39
55
  def normalize_gutter(gutter)
40
56
  return [ 0, 0 ] if gutter.nil?
41
- return [ gutter, gutter ] if gutter.is_a?(Numeric)
42
- return gutter if gutter.is_a?(Array) && gutter.size == 2
43
- [ 0, 0 ]
57
+ return [ gutter, gutter ] if gutter.is_a?(Integer)
58
+
59
+ gutter
44
60
  end
45
61
 
62
+ sig { returns(String) }
46
63
  def wrapper_classes
47
64
  class_names(
48
65
  "row",
@@ -51,10 +68,11 @@ module HakumiComponents
51
68
  "justify-#{@justify}": @justify != :start,
52
69
  "align-#{@align}": @align != :top
53
70
  },
54
- [ @html_options[:class] ]
71
+ html_classes(@html_options)
55
72
  )
56
73
  end
57
74
 
75
+ sig { returns(HakumiComponents::Types::HtmlAttributes) }
58
76
  def wrapper_attributes
59
77
  attrs = {
60
78
  class: wrapper_classes,
@@ -63,14 +81,14 @@ module HakumiComponents
63
81
  merge_attributes(attrs, @html_options.except(:class, :style))
64
82
  end
65
83
 
84
+ sig { returns(String) }
66
85
  def gutter_styles
67
86
  x, y = @gutter
68
- styles = []
87
+ styles = T.let([], T::Array[String])
69
88
  styles << "--row-gutter-x: #{x}px" if x > 0
70
89
  styles << "--row-gutter-y: #{y}px" if y > 0
71
90
 
72
- # Merge with existing style if present
73
- existing_style = @html_options[:style]
91
+ existing_style = html_style(@html_options)
74
92
  [ existing_style, styles.join(";") ].compact.join(";")
75
93
  end
76
94
  end
@@ -1,38 +1,28 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Icon
5
- # Icon component for rendering Ant Design icons
6
- #
7
- # Supports:
8
- # - Ant Design icons (via name)
9
- # - Custom SVG (via svg parameter)
10
- # - Three themes: outlined (default), filled, two-tone
11
- #
12
- # @example Basic usage
13
- # <%= render HakumiComponents::Icon::Component.new(name: "user") %>
14
- #
15
- # @example With size
16
- # <%= render HakumiComponents::Icon::Component.new(name: "home", size: 24) %>
17
- #
18
- # @example Filled variant
19
- # <%= render HakumiComponents::Icon::Component.new(name: "heart", theme: :filled) %>
20
- #
21
- # @example Custom SVG
22
- # <%= render HakumiComponents::Icon::Component.new(svg: "<svg>...</svg>") %>
23
6
  class Component < HakumiComponents::BaseComponent
24
- THEMES = [ :outlined, :filled, :two_tone ].freeze
25
- DEFAULT_SIZE = 16
26
- DEFAULT_COLOR = "currentColor"
27
-
28
- # @param name [String, Symbol] Icon name (e.g., :user, :home, :settings)
29
- # @param theme [Symbol] Icon theme (:outlined, :filled, :two_tone)
30
- # @param size [Integer] Icon size in pixels
31
- # @param svg [String] Custom SVG content (overrides name)
32
- # @param color [String] Icon color (CSS color value)
33
- # @param spin [Boolean] Whether the icon should spin
34
- # @param rotate [Integer] Rotation in degrees (0, 90, 180, 270)
35
- # @param html_options [Hash] Additional HTML attributes
7
+ extend T::Sig
8
+
9
+ THEMES = T.let([ :outlined, :filled, :two_tone ].freeze, T::Array[Symbol])
10
+ DEFAULT_SIZE = T.let(16, Integer)
11
+ DEFAULT_COLOR = T.let("currentColor", String)
12
+
13
+ sig do
14
+ params(
15
+ name: T.nilable(Types::StringOrSymbol),
16
+ theme: Symbol,
17
+ size: T.nilable(Integer),
18
+ svg: T.nilable(String),
19
+ color: T.nilable(String),
20
+ spin: T::Boolean,
21
+ rotate: T.nilable(Integer),
22
+ fallback_to_outlined: T::Boolean,
23
+ html_options: Types::HtmlAttributeValue
24
+ ).void
25
+ end
36
26
  def initialize(
37
27
  name: nil,
38
28
  theme: :outlined,
@@ -46,54 +36,55 @@ module HakumiComponents
46
36
  )
47
37
  @name = name
48
38
  @theme = theme
49
- @size = size
39
+ @size = T.let(size, T.nilable(Integer))
50
40
  @svg = svg
51
- @color = color || DEFAULT_COLOR
52
- @spin = spin
41
+ @color = T.let(color || DEFAULT_COLOR, String)
42
+ @spin = T.let(spin, T::Boolean)
53
43
  @rotate = rotate
54
- @fallback_to_outlined = fallback_to_outlined
55
- @html_options = html_options
44
+ @fallback_to_outlined = T.let(fallback_to_outlined, T::Boolean)
45
+ @html_options = T.let(html_options, Types::HtmlAttributes)
56
46
 
57
47
  validate_props!
58
48
  end
59
49
 
60
50
  private
61
51
 
52
+ sig { void }
62
53
  def validate_props!
63
- # Either name or svg must be provided
64
54
  unless @name || @svg
65
55
  raise ArgumentError, "Either name or svg must be provided"
66
56
  end
67
57
 
68
- # Validate theme
69
58
  validate_inclusion!(:theme, THEMES)
70
59
 
71
- # Validate rotate if present
72
60
  if @rotate && ![ 0, 90, 180, 270 ].include?(@rotate)
73
61
  raise ArgumentError, "rotate must be one of [0, 90, 180, 270]"
74
62
  end
75
63
  end
76
64
 
65
+ sig { returns(String) }
77
66
  def wrapper_classes
78
67
  class_names(
79
68
  "icon",
80
69
  { spin: @spin },
81
- [ @html_options[:class] ]
70
+ [ html_class_name ]
82
71
  )
83
72
  end
84
73
 
74
+ sig { returns(T.nilable(String)) }
85
75
  def wrapper_styles
86
- styles = []
76
+ styles = T.let([], T::Array[String])
87
77
  styles << "font-size: #{@size}px" if @size
88
78
  styles << "color: #{@color}" if @color
89
79
  styles << "transform: rotate(#{@rotate}deg)" if @rotate
90
80
 
91
- custom_style = @html_options[:style]
81
+ custom_style = html_style_value
92
82
  styles << custom_style if custom_style
93
83
 
94
84
  styles.join("; ") if styles.any?
95
85
  end
96
86
 
87
+ sig { returns(Types::HtmlAttributes) }
97
88
  def wrapper_attributes
98
89
  attrs = {
99
90
  class: wrapper_classes,
@@ -106,39 +97,34 @@ module HakumiComponents
106
97
  merge_attributes(attrs, @html_options.except(:class, :style))
107
98
  end
108
99
 
100
+ sig { returns(ActiveSupport::SafeBuffer) }
109
101
  def svg_content
110
102
  if @svg
111
- # Use custom SVG with sanitization
112
103
  sanitize_svg(@svg).html_safe
113
104
  else
114
- # Load SVG from icons directory
115
105
  icon_svg
116
106
  end
117
107
  end
118
108
 
119
- # Sanitize SVG content to prevent XSS attacks
120
- # Removes dangerous elements (script, iframe) and event handlers
121
- # This is a blacklist approach that preserves SVG functionality while preventing XSS
109
+ sig { params(svg_string: String).returns(String) }
122
110
  def sanitize_svg(svg_string)
123
111
  return "" if svg_string.blank?
124
112
 
125
- # Remove dangerous tags
126
113
  sanitized = svg_string.dup
127
114
  sanitized.gsub!(/<script[^>]*>.*?<\/script>/mi, "")
128
115
  sanitized.gsub!(/<iframe[^>]*>.*?<\/iframe>/mi, "")
129
116
  sanitized.gsub!(/<object[^>]*>.*?<\/object>/mi, "")
130
117
  sanitized.gsub!(/<embed[^>]*>/i, "")
131
118
 
132
- # Remove event handler attributes (onclick, onerror, onload, etc.)
133
119
  sanitized.gsub!(/\s+on\w+\s*=\s*["'][^"']*["']/i, "")
134
120
  sanitized.gsub!(/\s+on\w+\s*=\s*[^\s>]+/i, "")
135
121
 
136
- # Remove javascript: protocol in href and other URL attributes
137
122
  sanitized.gsub!(/\s+(href|src|xlink:href)\s*=\s*["']javascript:[^"']*["']/i, "")
138
123
 
139
124
  sanitized
140
125
  end
141
126
 
127
+ sig { returns(ActiveSupport::SafeBuffer) }
142
128
  def icon_svg
143
129
  svg = HakumiComponents::Icon::Loader.load(@name, @theme)
144
130
  if svg.blank? && fallback_enabled?
@@ -150,6 +136,7 @@ module HakumiComponents
150
136
  placeholder_icon_svg
151
137
  end
152
138
 
139
+ sig { returns(ActiveSupport::SafeBuffer) }
153
140
  def placeholder_icon_svg
154
141
  <<~SVG.html_safe
155
142
  <svg viewBox="0 0 24 24" focusable="false" width="1em" height="1em" fill="currentColor">
@@ -159,9 +146,22 @@ module HakumiComponents
159
146
  SVG
160
147
  end
161
148
 
149
+ sig { returns(T::Boolean) }
162
150
  def fallback_enabled?
163
151
  @fallback_to_outlined && @theme != :outlined
164
152
  end
153
+
154
+ sig { returns(T.nilable(String)) }
155
+ def html_class_name
156
+ css_class = @html_options[:class]
157
+ css_class.is_a?(String) ? css_class : nil
158
+ end
159
+
160
+ sig { returns(T.nilable(String)) }
161
+ def html_style_value
162
+ style = @html_options[:style]
163
+ style.is_a?(String) ? style : nil
164
+ end
165
165
  end
166
166
  end
167
167
  end
@@ -22,7 +22,7 @@
22
22
  <% else %>
23
23
  <div class="hakumi-image-mask-info">
24
24
  <%= render HakumiComponents::Icon::Component.new(name: :eye, size: 16) %>
25
- <span>Preview</span>
25
+ <span><%= preview_text %></span>
26
26
  </div>
27
27
  <% end %>
28
28
  </div>
@@ -42,34 +42,34 @@
42
42
  </span>
43
43
  </button>
44
44
  <div class="hakumi-image-preview-operations">
45
- <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleFlipY" title="Flip Vertical">
45
+ <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleFlipY" title="<%= flip_vertical_title %>">
46
46
  <span class="hakumi-icon" style="transform: rotate(90deg)">
47
47
  <%= render HakumiComponents::Icon::Component.new(name: 'swap', theme: :outlined) %>
48
48
  </span>
49
49
  </button>
50
- <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleFlipX" title="Flip Horizontal">
50
+ <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleFlipX" title="<%= flip_horizontal_title %>">
51
51
  <span class="hakumi-icon">
52
52
  <%= render HakumiComponents::Icon::Component.new(name: 'swap', theme: :outlined) %>
53
53
  </span>
54
54
  </button>
55
55
  <span class="hakumi-image-preview-operations-divider"></span>
56
- <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleRotateLeft" title="Rotate Left">
56
+ <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleRotateLeft" title="<%= rotate_left_title %>">
57
57
  <span class="hakumi-icon">
58
58
  <%= render HakumiComponents::Icon::Component.new(name: 'rotate-left', theme: :outlined) %>
59
59
  </span>
60
60
  </button>
61
- <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleRotateRight" title="Rotate Right">
61
+ <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleRotateRight" title="<%= rotate_right_title %>">
62
62
  <span class="hakumi-icon">
63
63
  <%= render HakumiComponents::Icon::Component.new(name: 'rotate-right', theme: :outlined) %>
64
64
  </span>
65
65
  </button>
66
66
  <span class="hakumi-image-preview-operations-divider"></span>
67
- <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleZoomOut" title="Zoom Out">
67
+ <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleZoomOut" title="<%= zoom_out_title %>">
68
68
  <span class="hakumi-icon">
69
69
  <%= render HakumiComponents::Icon::Component.new(name: 'zoom-out', theme: :outlined) %>
70
70
  </span>
71
71
  </button>
72
- <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleZoomIn" title="Zoom In">
72
+ <button class="hakumi-image-preview-operations-operation" data-action="click->hakumi--image#handleZoomIn" title="<%= zoom_in_title %>">
73
73
  <span class="hakumi-icon">
74
74
  <%= render HakumiComponents::Icon::Component.new(name: 'zoom-in', theme: :outlined) %>
75
75
  </span>