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,61 +1,66 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module HakumiComponents
4
5
  module Progress
5
6
  class Component < HakumiComponents::BaseComponent
6
- TYPES = [ :line, :circle, :dashboard ].freeze
7
- STATUSES = [ :normal, :success, :exception, :active ].freeze
8
- LINE_SIZES = [ :small, :default ].freeze
9
- LINECAPS = [ :round, :square, :butt ].freeze
10
- CIRCLE_STEP_GAP = 2
11
-
12
- def self.extract_controller_locals(params)
13
- {
14
- percent: parse_number(params[:percent]),
15
- success_percent: parse_number(params[:success_percent]),
16
- type: params[:type]&.to_sym,
17
- status: params[:status]&.to_sym,
18
- show_info: params.key?(:show_info) ? ActiveModel::Type::Boolean.new.cast(params[:show_info]) : nil,
19
- size: parse_size(params[:size]),
20
- stroke_width: parse_number(params[:stroke_width]),
21
- stroke_color: parse_stroke_color(params[:stroke_color]),
22
- trail_color: params[:trail_color],
23
- stroke_linecap: params[:stroke_linecap]&.to_sym,
24
- steps: params[:steps].presence&.to_i,
25
- gap_degree: parse_number(params[:gap_degree]),
26
- gap_position: params[:gap_position]&.to_sym,
27
- format: params[:format],
28
- info_tooltip: params[:info_tooltip],
29
- info_tooltip_threshold: parse_number(params[:info_tooltip_threshold]),
30
- info_tooltip_options: params[:info_tooltip_options]
31
- }
32
- end
33
-
34
- def self.parse_number(value)
35
- return nil if value.nil?
36
- return value if value.is_a?(Numeric)
37
-
38
- value.to_f if value.to_s.strip != ""
7
+ extend T::Sig
8
+
9
+ ProgressValue = T.type_alias { Numeric }
10
+ ProgressOption = T.type_alias { Types::HtmlKey }
11
+ InfoTooltipInput = T.type_alias { T.nilable(T.any(Types::HtmlKey, T::Boolean)) }
12
+ DisplaySize = T.type_alias { T.nilable(BaseComponent::SizeValue) }
13
+ StrokeGradient = T.type_alias { T::Hash[String, String] }
14
+ StrokeColorInput = T.type_alias { T.nilable(T.any(String, Types::DataAttributes, StrokeGradient)) }
15
+ StrokeColor = T.type_alias { T.nilable(T.any(String, StrokeGradient)) }
16
+ FormatProc = T.type_alias do
17
+ T.proc.params(
18
+ percent: ProgressValue,
19
+ success_percent: T.nilable(ProgressValue)
20
+ ).returns(String)
21
+ end
22
+ FormatValue = T.type_alias { T.nilable(T.any(Types::Renderable, FormatProc)) }
23
+ TooltipMode = T.type_alias { T.nilable(Symbol) }
24
+ TooltipOptionsMap = T.type_alias { T::Hash[Types::HtmlKey, Types::HtmlPrimitive] }
25
+ StepStateValue = T.type_alias { Types::HtmlPrimitive }
26
+ StepState = T.type_alias { T::Hash[Symbol, StepStateValue] }
27
+ SizeInput = T.type_alias { DisplaySize }
28
+ TYPES = T.let(%i[line circle dashboard].freeze, T::Array[Symbol])
29
+ STATUSES = T.let(%i[normal success exception active].freeze, T::Array[Symbol])
30
+ LINE_SIZES = T.let(%i[small default].freeze, T::Array[Symbol])
31
+ LINECAPS = T.let(%i[round square butt].freeze, T::Array[Symbol])
32
+
33
+ sig do
34
+ params(
35
+ params: BaseComponent::ControllerOptions
36
+ ).returns(Types::HtmlAttributes)
39
37
  end
40
-
41
- def self.parse_size(value)
42
- return nil if value.nil?
43
- return value if value.is_a?(Numeric)
44
-
45
- return value.to_sym if value.is_a?(String) && LINE_SIZES.include?(value.to_sym)
46
-
47
- value.to_i if value.to_s.strip != ""
48
- end
49
-
50
- def self.parse_stroke_color(value)
51
- return nil if value.nil?
52
-
53
- return value.to_unsafe_h if value.respond_to?(:to_unsafe_h)
54
- return value if value.is_a?(Hash)
55
-
56
- value
38
+ def self.extract_controller_locals(params)
39
+ ControllerLocalsParser.call(params)
40
+ end
41
+
42
+ sig do
43
+ params(
44
+ percent: ProgressValue,
45
+ success_percent: T.nilable(ProgressValue),
46
+ type: ProgressOption,
47
+ status: T.nilable(ProgressOption),
48
+ show_info: T::Boolean,
49
+ size: DisplaySize,
50
+ stroke_width: T.nilable(ProgressValue),
51
+ stroke_color: StrokeColorInput,
52
+ trail_color: T.nilable(String),
53
+ stroke_linecap: ProgressOption,
54
+ steps: T.nilable(Integer),
55
+ gap_degree: T.nilable(ProgressValue),
56
+ gap_position: T.nilable(ProgressOption),
57
+ format: FormatValue,
58
+ info_tooltip: InfoTooltipInput,
59
+ info_tooltip_threshold: ProgressValue,
60
+ info_tooltip_options: T.nilable(TooltipOptionsMap),
61
+ html_options: Types::HtmlAttributeValue
62
+ ).void
57
63
  end
58
-
59
64
  def initialize(
60
65
  percent: 0,
61
66
  success_percent: nil,
@@ -73,32 +78,35 @@ module HakumiComponents
73
78
  format: nil,
74
79
  info_tooltip: :auto,
75
80
  info_tooltip_threshold: 20,
76
- info_tooltip_options: {},
81
+ info_tooltip_options: nil,
77
82
  **html_options
78
83
  )
79
- @type = type
80
- @status = status
81
- @show_info = show_info.nil? ? true : show_info
82
- @size = size
83
- @stroke_width = stroke_width
84
- @stroke_color = stroke_color
85
- @trail_color = trail_color
86
- @stroke_linecap = stroke_linecap
87
- @steps = steps
88
- @gap_degree = gap_degree
89
- @gap_position = gap_position
90
- @format = format
91
- @html_options = html_options
92
- @info_tooltip_mode = normalize_info_tooltip(info_tooltip)
93
- @info_tooltip_threshold = info_tooltip_threshold
94
- @info_tooltip_options = normalize_tooltip_options(info_tooltip_options)
95
-
96
- @percent = clamp_percent(percent)
97
- @success_percent = success_percent.nil? ? nil : clamp_percent(success_percent)
84
+ @type = T.let(type.to_sym, Symbol)
85
+ @status = T.let(status&.to_sym, T.nilable(Symbol))
86
+ @show_info = T.let(cast_boolean(show_info) != false, T::Boolean)
87
+ @size = T.let(normalize_size(size), DisplaySize)
88
+ @stroke_width = T.let(stroke_width&.to_f, T.nilable(Float))
89
+ @stroke_color = T.let(normalize_stroke_color(stroke_color), StrokeColor)
90
+ @trail_color = T.let(trail_color&.strip&.presence, T.nilable(String))
91
+ @stroke_linecap = T.let(stroke_linecap.to_sym, Symbol)
92
+ @steps = T.let(steps, T.nilable(Integer))
93
+ @gap_degree = T.let(gap_degree&.to_f, T.nilable(Float))
94
+ @gap_position = T.let(gap_position&.to_sym, T.nilable(Symbol))
95
+ @format = T.let(format, FormatValue)
96
+ @html_options = T.let(html_options, Types::HtmlAttributes)
97
+ @info_tooltip_mode = T.let(normalize_info_tooltip(info_tooltip), TooltipMode)
98
+ @info_tooltip_threshold = T.let(info_tooltip_threshold.to_f, Float)
99
+ @info_tooltip_options = T.let(normalize_tooltip_options(info_tooltip_options), Types::HtmlAttributes)
100
+ @percent = T.let(clamp_percent(percent), ProgressValue)
101
+ @success_percent = T.let(success_percent.nil? ? nil : clamp_percent(success_percent), T.nilable(ProgressValue))
102
+ @info_tooltip_policy = T.let(nil, T.nilable(Progress::InfoTooltipPolicy))
103
+ @status_state = T.let(nil, T.nilable(Progress::StatusState))
104
+ @attribute_renderer = T.let(nil, T.nilable(Progress::AttributeRenderer))
98
105
 
99
106
  validate_props!
100
107
  end
101
108
 
109
+ sig { returns(Types::HtmlAttributes) }
102
110
  def wrapper_attributes
103
111
  merge_attributes(
104
112
  {
@@ -114,208 +122,76 @@ module HakumiComponents
114
122
  )
115
123
  end
116
124
 
125
+ sig { returns(Types::RenderedContent) }
117
126
  def formatted_info
118
127
  return nil unless show_info?
128
+
119
129
  info_text
120
130
  end
121
131
 
132
+ sig { returns(T::Boolean) }
122
133
  def show_info?
123
134
  @show_info
124
135
  end
125
136
 
137
+ sig { returns(T::Boolean) }
126
138
  def inline_info?
127
139
  show_info? && !wrap_with_tooltip?
128
140
  end
129
141
 
142
+ sig { returns(T::Boolean) }
130
143
  def wrap_with_tooltip?
131
- return false unless tooltip_enabled?
132
-
133
- case @info_tooltip_mode
134
- when :always
135
- true
136
- when :auto
137
- circle? && circle_size <= info_tooltip_threshold_value
138
- else
139
- false
140
- end
144
+ info_tooltip_policy.wrap?
141
145
  end
142
146
 
147
+ sig { returns(Types::HtmlAttributes) }
143
148
  def tooltip_component_options
144
- options = (@info_tooltip_options || {}).dup
145
- options[:title] ||= tooltip_label
149
+ options = T.let(@info_tooltip_options.dup, Types::HtmlAttributes)
150
+ options[:title] = tooltip_label unless options.key?(:title)
146
151
  options
147
152
  end
148
153
 
154
+ sig { returns(Integer) }
149
155
  def info_tooltip_threshold_value
150
- (@info_tooltip_threshold || 20).to_i
156
+ @info_tooltip_threshold.to_i
151
157
  end
152
158
 
159
+ sig { returns(String) }
153
160
  def tooltip_label
154
- info = show_info? ? formatted_info : nil
155
- info.presence || "#{@percent.to_i}%"
161
+ info_value.tooltip_label
156
162
  end
157
163
 
164
+ sig { returns(T::Boolean) }
158
165
  def tooltip_enabled?
159
- @info_tooltip_mode && @info_tooltip_mode != false
166
+ info_tooltip_policy.enabled?
160
167
  end
161
168
 
169
+ sig { returns(T::Boolean) }
162
170
  def line?
163
171
  @type == :line
164
172
  end
165
173
 
174
+ sig { returns(T::Boolean) }
166
175
  def circle?
167
176
  @type == :circle
168
177
  end
169
178
 
179
+ sig { returns(T::Boolean) }
170
180
  def dashboard?
171
181
  @type == :dashboard
172
182
  end
173
183
 
184
+ sig { returns(T::Boolean) }
174
185
  def steps?
175
- @steps.to_i.positive?
176
- end
177
-
178
- def line_style
179
- build_inline_style(
180
- "--hakumi-progress-stroke-width" => "#{line_stroke_width}px",
181
- "--hakumi-progress-trail-color" => @trail_color
182
- )
183
- end
184
-
185
- def line_bar_style
186
- styles = { "width" => "#{@percent}%" }
187
- if gradient?
188
- styles["background"] = gradient_css
189
- elsif @stroke_color.present?
190
- styles["background-color"] = @stroke_color
191
- end
192
- build_inline_style(styles)
193
- end
194
-
195
- def line_success_style
196
- build_inline_style("width" => "#{@success_percent}%")
197
- end
198
-
199
- def line_steps
200
- return [] unless line? && steps?
201
-
202
- total = @steps.to_i
203
- progress_units = (@percent / 100.0) * total
204
- (0...total).map do |index|
205
- active = progress_units > index
206
- current = index == progress_units.floor && progress_units < total
207
- {
208
- active: active,
209
- current: current,
210
- width: "#{(100.0 / total).round(2)}%",
211
- custom_color: steps_custom_color
212
- }
213
- end
214
- end
215
-
216
- def circle_size
217
- return @size.to_i if @size.is_a?(Numeric)
218
-
219
- 120
220
- end
221
-
222
- def circle_inner_style
223
- build_inline_style(
224
- "width" => "#{circle_size}px",
225
- "height" => "#{circle_size}px"
226
- )
227
- end
228
-
229
- def circle_trail_attributes
230
- return {} if circle_steps?
231
-
232
- {
233
- class: "hakumi-progress-circle-trail",
234
- cx: 50,
235
- cy: 50,
236
- r: circle_radius,
237
- fill: "none",
238
- stroke: "var(--hakumi-progress-trail-color, var(--color-border-secondary))",
239
- "stroke-width": circle_stroke_width,
240
- "stroke-dasharray": circle_trail_dasharray,
241
- "stroke-dashoffset": circle_dashoffset,
242
- transform: circle_transform
243
- }
244
- end
245
-
246
- def circle_path_attributes
247
- return {} if circle_steps?
248
-
249
- attrs = {
250
- class: "hakumi-progress-circle-path",
251
- cx: 50,
252
- cy: 50,
253
- r: circle_radius,
254
- fill: "none",
255
- "stroke-linecap": @stroke_linecap,
256
- "stroke-width": circle_stroke_width,
257
- "stroke-dasharray": circle_path_dasharray,
258
- "stroke-dashoffset": circle_dashoffset,
259
- transform: circle_transform
260
- }
261
-
262
- attrs[:style] = build_inline_style("stroke" => circle_stroke) if circle_stroke
263
- attrs
264
- end
265
-
266
- def circle_trail_style
267
- build_inline_style("--hakumi-progress-trail-color" => @trail_color)
268
- end
269
-
270
- def circle_gradient_attributes
271
- gradient_stops
272
- end
273
-
274
- def gradient_id
275
- @gradient_id ||= generate_id("hakumi-progress-gradient")
276
- end
277
-
278
- def circle_stroke
279
- return "url(##{gradient_id})" if gradient?
280
- return @stroke_color if @stroke_color.present?
281
-
282
- nil
283
- end
284
-
285
- def gradient?
286
- @stroke_color.is_a?(Hash)
287
- end
288
-
289
- def gradient_css
290
- data = gradient_stops
291
- return nil unless data
292
-
293
- "linear-gradient(#{data[:direction]}, #{gradient_stop_list(data[:stops])})"
294
- end
295
-
296
- def gradient_svg_direction
297
- data = gradient_stops
298
- return { x1: "0%", y1: "0%", x2: "100%", y2: "0%" } unless data
299
-
300
- direction = data[:direction]
301
- case direction
302
- when "to left"
303
- { x1: "100%", y1: "0%", x2: "0%", y2: "0%" }
304
- when "to bottom"
305
- { x1: "0%", y1: "0%", x2: "0%", y2: "100%" }
306
- when "to top"
307
- { x1: "0%", y1: "100%", x2: "0%", y2: "0%" }
308
- else
309
- { x1: "0%", y1: "0%", x2: "100%", y2: "0%" }
310
- end
186
+ !@steps.nil? && @steps.positive?
311
187
  end
312
188
 
189
+ sig { returns(Symbol) }
313
190
  def status
314
- return @status if @status
315
-
316
- @percent >= 100 ? :success : :normal
191
+ status_state.value
317
192
  end
318
193
 
194
+ sig { returns(Types::DataAttributes) }
319
195
  def data_attributes
320
196
  {
321
197
  controller: "hakumi--progress",
@@ -323,32 +199,71 @@ module HakumiComponents
323
199
  "hakumi--progress-percent-value": @percent,
324
200
  "hakumi--progress-success-percent-value": @success_percent,
325
201
  "hakumi--progress-status-value": status,
326
- "hakumi--progress-status-explicit-value": @status.present?,
202
+ "hakumi--progress-status-explicit-value": status_state.explicit?,
327
203
  "hakumi--progress-steps-value": @steps,
328
204
  "hakumi--progress-gap-degree-value": @gap_degree,
329
- "hakumi--progress-gap-position-value": circle_gap_position,
330
- "hakumi--progress-stroke-width-value": stroke_width_value,
205
+ "hakumi--progress-gap-position-value": attribute_renderer.circle_gap_position,
206
+ "hakumi--progress-stroke-width-value": attribute_renderer.stroke_width_value,
331
207
  "hakumi--progress-show-info-value": show_info?,
332
208
  "hakumi--progress-custom-info-value": custom_info?
333
- }
209
+ }.compact
210
+ end
211
+
212
+ sig { returns(T::Boolean) }
213
+ def success_segment?
214
+ success = @success_percent
215
+ !success.nil? && success.positive?
334
216
  end
335
217
 
336
218
  private
337
219
 
220
+ sig { returns(Types::RenderedContent) }
338
221
  def info_text
339
- return content if content.present?
222
+ info_value.text
223
+ end
340
224
 
341
- if @format.respond_to?(:call)
342
- value = @format.call(@percent, @success_percent)
343
- return render_value(value)
344
- end
225
+ sig { returns(Progress::InfoTooltipPolicy) }
226
+ def info_tooltip_policy
227
+ policy = @info_tooltip_policy
228
+ return policy unless policy.nil?
229
+
230
+ policy = Progress::InfoTooltipPolicy.new(
231
+ mode: @info_tooltip_mode,
232
+ type: @type,
233
+ circle_size: attribute_renderer.circle_size,
234
+ threshold: info_tooltip_threshold_value
235
+ )
236
+ @info_tooltip_policy = policy
237
+ end
238
+
239
+ sig { returns(Progress::InfoValue) }
240
+ def info_value
241
+ Progress::InfoValue.new(
242
+ percent: @percent,
243
+ success_percent: @success_percent,
244
+ content: content,
245
+ format_value: @format,
246
+ rendered_format: rendered_info_format
247
+ )
248
+ end
345
249
 
346
- rendered_format = render_value(@format)
347
- return rendered_format if rendered_format.present?
250
+ sig { returns(T.untyped) }
251
+ def rendered_info_format
252
+ return nil if @format.nil? || @format.is_a?(Proc)
348
253
 
349
- "#{@percent.to_i}%"
254
+ render_value(@format)
350
255
  end
351
256
 
257
+ sig { returns(Progress::StatusState) }
258
+ def status_state
259
+ state = @status_state
260
+ return state unless state.nil?
261
+
262
+ state = Progress::StatusState.new(percent: @percent, explicit_status: @status)
263
+ @status_state = state
264
+ end
265
+
266
+ sig { void }
352
267
  def validate_props!
353
268
  validate_inclusion!(:type, TYPES)
354
269
  validate_inclusion!(:status, STATUSES)
@@ -363,202 +278,115 @@ module HakumiComponents
363
278
  end
364
279
  end
365
280
 
281
+ sig { returns(String) }
366
282
  def wrapper_classes
367
- modifiers = {
368
- @type => true,
369
- "status-#{status}" => true,
370
- "show-info" => show_info?,
371
- "linecap-#{@stroke_linecap}" => true
372
- }
373
-
374
- modifiers[:small] = true if line? && @size == :small
375
- modifiers[:steps] = true if steps?
283
+ modifiers = T.let(
284
+ {
285
+ @type => true,
286
+ "status-#{status}" => true,
287
+ "show-info" => show_info?,
288
+ "linecap-#{@stroke_linecap}" => true,
289
+ small: line? && @size == :small,
290
+ steps: steps?
291
+ },
292
+ Types::ClassModifiers
293
+ )
376
294
 
377
- class_names("progress", modifiers, [ @html_options[:class] ])
295
+ component_classes("progress", modifiers, @html_options)
378
296
  end
379
297
 
298
+ sig { returns(String) }
380
299
  def accessibility_label
381
- "Progress #{@percent.to_i}%"
300
+ "Progress #{percent_label}"
382
301
  end
383
302
 
303
+ sig { params(value: ProgressValue).returns(ProgressValue) }
384
304
  def clamp_percent(value)
385
- value = value.to_f
386
- return 0 if value.nan?
387
-
388
- clamped = value.clamp(0, 100)
389
- clamped == clamped.to_i ? clamped.to_i : clamped
390
- end
391
-
392
- def line_stroke_width
393
- return @stroke_width if @stroke_width
394
-
395
- return 6 if @size == :small
396
-
397
- 8
398
- end
399
-
400
- def steps_custom_color
401
- return @stroke_color if @stroke_color.present? && !gradient?
402
-
403
- nil
404
- end
405
-
406
- def circle_stroke_width
407
- stroke_width_value
408
- end
409
-
410
- def circle_radius
411
- 50 - (circle_stroke_width / 2.0)
412
- end
413
-
414
- def circle_total_length
415
- 2 * Math::PI * circle_radius
416
- end
417
-
418
- def circle_gap_degree
419
- return @gap_degree if @gap_degree
420
-
421
- dashboard? ? 75 : 0
422
- end
423
-
424
- def circle_gap_position
425
- return @gap_position if @gap_position
426
-
427
- dashboard? ? :bottom : :top
428
- end
429
-
430
- def circle_gap_length
431
- circle_total_length * (circle_gap_degree / 360.0)
432
- end
433
-
434
- def circle_effective_length
435
- circle_total_length - circle_gap_length
436
- end
437
-
438
- def circle_path_length
439
- circle_effective_length * (@percent / 100.0)
440
- end
441
-
442
- def circle_trail_length
443
- circle_effective_length
444
- end
445
-
446
- def circle_dashoffset
447
- format_float(circle_gap_length / 2.0)
448
- end
305
+ numeric = value.to_f
306
+ return 0 if numeric.nan?
449
307
 
450
- def circle_path_dasharray
451
- "#{format_float(circle_path_length)} #{format_float(circle_total_length)}"
308
+ clamped = numeric.clamp(0, 100)
309
+ whole_number?(clamped) ? clamped.to_i : clamped
452
310
  end
453
311
 
454
- def circle_trail_dasharray
455
- "#{format_float(circle_trail_length)} #{format_float(circle_total_length)}"
312
+ sig { returns(T::Boolean) }
313
+ def custom_info?
314
+ info_value.custom?
456
315
  end
457
316
 
458
- def circle_transform
459
- "rotate(#{circle_rotation} 50 50)"
460
- end
317
+ sig { params(value: InfoTooltipInput).returns(TooltipMode) }
318
+ def normalize_info_tooltip(value)
319
+ string_value = value.is_a?(String) ? value.strip.downcase : value
461
320
 
462
- def circle_rotation
463
- case circle_gap_position
464
- when :left then 180
465
- when :right then 0
466
- when :bottom then 90
467
- else -90
468
- end
469
- end
321
+ return :always if string_value == :always || string_value == "always"
322
+ return :auto if string_value == :auto || string_value.nil?
470
323
 
471
- def format_float(value)
472
- format("%.2f", value)
324
+ cast_boolean(value) ? :auto : nil
473
325
  end
474
326
 
475
- def gradient_stops
476
- return nil unless gradient?
477
-
478
- raw = @stroke_color.transform_keys(&:to_s)
479
- direction = raw.delete("direction") || "to right"
327
+ sig { params(value: T.nilable(TooltipOptionsMap)).returns(Types::HtmlAttributes) }
328
+ def normalize_tooltip_options(value)
329
+ options = T.let({}, Types::HtmlAttributes)
330
+ return options if value.nil?
480
331
 
481
- stops = if raw.key?("from") || raw.key?("to")
482
- [ [ "0%", raw["from"] ], [ "100%", raw["to"] ] ].compact
483
- else
484
- raw.map { |key, val| [ key, val ] }
332
+ value.each do |key, entry_value|
333
+ options[key.to_sym] = entry_value
485
334
  end
486
-
487
- stops = stops.reject { |_key, val| val.nil? }
488
- return nil if stops.empty?
489
-
490
- { direction: direction, stops: stops }
491
- end
492
-
493
- def gradient_stop_list(stops)
494
- stops.map { |key, color| "#{color} #{key}" }.join(", ")
495
- end
496
-
497
- def stroke_width_value
498
- return @stroke_width if @stroke_width
499
-
500
- circle? || dashboard? ? 6 : line_stroke_width
335
+ options
501
336
  end
502
337
 
503
- def custom_info?
504
- content.present? || @format.present?
505
- end
338
+ sig { params(value: SizeInput).returns(DisplaySize) }
339
+ def normalize_size(value)
340
+ return value if value.nil? || value.is_a?(Integer)
341
+ return value.to_sym if value.is_a?(String) && LINE_SIZES.include?(value.to_sym)
342
+ return value if value.is_a?(Symbol) && LINE_SIZES.include?(value)
506
343
 
507
- def circle_steps?
508
- (circle? || dashboard?) && steps?
344
+ raise ArgumentError, "size must be one of #{LINE_SIZES.inspect} or a numeric value"
509
345
  end
510
346
 
511
- def circle_steps
512
- return [] unless circle_steps?
513
-
514
- total = @steps.to_i
515
- gap = circle_step_gap
516
- effective = circle_effective_length
517
- segment_length = (effective - (gap * total)) / total.to_f
518
- progress_units = (@percent / 100.0) * total
519
-
520
- (0...total).map do |index|
521
- active = progress_units > index
522
- current = index == progress_units.floor && progress_units < total
523
- offset = circle_dashoffset.to_f + ((segment_length + gap) * index)
524
- {
525
- index: index,
526
- active: active,
527
- current: current,
528
- dasharray: "#{format_float(segment_length)} #{format_float(circle_total_length)}",
529
- dashoffset: format_float(offset),
530
- stroke: circle_step_stroke(active)
531
- }
532
- end
533
- end
347
+ sig { params(value: StrokeColorInput).returns(StrokeColor) }
348
+ def normalize_stroke_color(value)
349
+ return nil if value.nil?
350
+ return value.strip.presence if value.is_a?(String)
534
351
 
535
- def circle_step_gap
536
- CIRCLE_STEP_GAP
352
+ Progress::StrokeGradientValue.normalize(value)
537
353
  end
538
354
 
539
- def circle_step_stroke(active)
540
- return "var(--hakumi-progress-trail-color, var(--color-border-secondary))" unless active
541
- return "url(##{gradient_id})" if gradient?
542
- return @stroke_color if @stroke_color.present?
355
+ sig { returns(Progress::AttributeRenderer) }
356
+ def attribute_renderer
357
+ renderer = @attribute_renderer
358
+ return renderer unless renderer.nil?
543
359
 
544
- nil
360
+ style_builder = T.let(
361
+ ->(styles) { build_inline_style(styles) },
362
+ Progress::AttributeRenderer::StyleBuilder
363
+ )
364
+ renderer = Progress::AttributeRenderer.new(
365
+ type: @type,
366
+ percent: @percent,
367
+ success_percent: @success_percent,
368
+ size: @size,
369
+ stroke_width: @stroke_width,
370
+ stroke_color: @stroke_color,
371
+ trail_color: @trail_color,
372
+ stroke_linecap: @stroke_linecap,
373
+ steps: @steps,
374
+ gap_degree: @gap_degree,
375
+ gap_position: @gap_position,
376
+ gradient_id: generate_id("hakumi-progress-gradient"),
377
+ style_builder: style_builder
378
+ )
379
+ @attribute_renderer = renderer
545
380
  end
546
381
 
547
- def normalize_info_tooltip(value)
548
- string_value = value.is_a?(String) ? value.strip.downcase : value
549
-
550
- return :always if string_value == :always || string_value == "always"
551
- return :auto if string_value == :auto || string_value.nil?
552
-
553
- ActiveModel::Type::Boolean.new.cast(value) ? :auto : false
382
+ sig { returns(String) }
383
+ def percent_label
384
+ info_value.percent_label
554
385
  end
555
386
 
556
- def normalize_tooltip_options(value)
557
- options = value || {}
558
- options = options.to_unsafe_h if options.respond_to?(:to_unsafe_h)
559
- options = options.to_h unless options.is_a?(Hash)
560
- options.deep_symbolize_keys! if options.respond_to?(:deep_symbolize_keys)
561
- options
387
+ sig { params(value: ProgressValue).returns(T::Boolean) }
388
+ def whole_number?(value)
389
+ (value.to_f % 1.0).zero?
562
390
  end
563
391
  end
564
392
  end