phlex_kit 0.2.0

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 (405) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +135 -0
  4. data/app/assets/stylesheets/phlex_kit/_tokens.css +91 -0
  5. data/app/assets/stylesheets/phlex_kit/phlex_kit.css +82 -0
  6. data/app/components/phlex_kit/accordion/accordion.css +14 -0
  7. data/app/components/phlex_kit/accordion/accordion.rb +10 -0
  8. data/app/components/phlex_kit/accordion/accordion_content.rb +8 -0
  9. data/app/components/phlex_kit/accordion/accordion_default_content.rb +6 -0
  10. data/app/components/phlex_kit/accordion/accordion_default_trigger.rb +12 -0
  11. data/app/components/phlex_kit/accordion/accordion_icon.rb +16 -0
  12. data/app/components/phlex_kit/accordion/accordion_item.rb +12 -0
  13. data/app/components/phlex_kit/accordion/accordion_trigger.rb +8 -0
  14. data/app/components/phlex_kit/alert/alert.css +32 -0
  15. data/app/components/phlex_kit/alert/alert.rb +37 -0
  16. data/app/components/phlex_kit/alert/alert_description.rb +12 -0
  17. data/app/components/phlex_kit/alert/alert_title.rb +12 -0
  18. data/app/components/phlex_kit/alert_dialog/alert_dialog.css +37 -0
  19. data/app/components/phlex_kit/alert_dialog/alert_dialog.rb +20 -0
  20. data/app/components/phlex_kit/alert_dialog/alert_dialog_action.rb +14 -0
  21. data/app/components/phlex_kit/alert_dialog/alert_dialog_cancel.rb +13 -0
  22. data/app/components/phlex_kit/alert_dialog/alert_dialog_content.rb +19 -0
  23. data/app/components/phlex_kit/alert_dialog/alert_dialog_description.rb +12 -0
  24. data/app/components/phlex_kit/alert_dialog/alert_dialog_footer.rb +12 -0
  25. data/app/components/phlex_kit/alert_dialog/alert_dialog_header.rb +12 -0
  26. data/app/components/phlex_kit/alert_dialog/alert_dialog_title.rb +12 -0
  27. data/app/components/phlex_kit/alert_dialog/alert_dialog_trigger.rb +12 -0
  28. data/app/components/phlex_kit/aspect_ratio/aspect_ratio.css +12 -0
  29. data/app/components/phlex_kit/aspect_ratio/aspect_ratio.rb +27 -0
  30. data/app/components/phlex_kit/attachment/attachment.css +62 -0
  31. data/app/components/phlex_kit/attachment/attachment.rb +16 -0
  32. data/app/components/phlex_kit/attachment/attachment_action.rb +24 -0
  33. data/app/components/phlex_kit/attachment/attachment_actions.rb +7 -0
  34. data/app/components/phlex_kit/attachment/attachment_content.rb +7 -0
  35. data/app/components/phlex_kit/attachment/attachment_description.rb +7 -0
  36. data/app/components/phlex_kit/attachment/attachment_media.rb +8 -0
  37. data/app/components/phlex_kit/attachment/attachment_title.rb +7 -0
  38. data/app/components/phlex_kit/avatar/avatar.css +35 -0
  39. data/app/components/phlex_kit/avatar/avatar.rb +25 -0
  40. data/app/components/phlex_kit/avatar/avatar_fallback.rb +13 -0
  41. data/app/components/phlex_kit/avatar/avatar_group.rb +7 -0
  42. data/app/components/phlex_kit/avatar/avatar_image.rb +24 -0
  43. data/app/components/phlex_kit/badge/badge.css +50 -0
  44. data/app/components/phlex_kit/badge/badge.rb +39 -0
  45. data/app/components/phlex_kit/breadcrumb/breadcrumb.css +18 -0
  46. data/app/components/phlex_kit/breadcrumb/breadcrumb.rb +8 -0
  47. data/app/components/phlex_kit/breadcrumb/breadcrumb_ellipsis.rb +15 -0
  48. data/app/components/phlex_kit/breadcrumb/breadcrumb_item.rb +6 -0
  49. data/app/components/phlex_kit/breadcrumb/breadcrumb_link.rb +9 -0
  50. data/app/components/phlex_kit/breadcrumb/breadcrumb_list.rb +6 -0
  51. data/app/components/phlex_kit/breadcrumb/breadcrumb_page.rb +8 -0
  52. data/app/components/phlex_kit/breadcrumb/breadcrumb_separator.rb +10 -0
  53. data/app/components/phlex_kit/bubble/bubble.css +32 -0
  54. data/app/components/phlex_kit/bubble/bubble.rb +17 -0
  55. data/app/components/phlex_kit/bubble/bubble_content.rb +11 -0
  56. data/app/components/phlex_kit/bubble/bubble_group.rb +6 -0
  57. data/app/components/phlex_kit/bubble/bubble_reactions.rb +12 -0
  58. data/app/components/phlex_kit/button/button.css +72 -0
  59. data/app/components/phlex_kit/button/button.rb +51 -0
  60. data/app/components/phlex_kit/button_group/button_group.css +8 -0
  61. data/app/components/phlex_kit/button_group/button_group.rb +14 -0
  62. data/app/components/phlex_kit/calendar/calendar.css +109 -0
  63. data/app/components/phlex_kit/calendar/calendar.rb +47 -0
  64. data/app/components/phlex_kit/calendar/calendar_body.rb +13 -0
  65. data/app/components/phlex_kit/calendar/calendar_days.rb +98 -0
  66. data/app/components/phlex_kit/calendar/calendar_header.rb +13 -0
  67. data/app/components/phlex_kit/calendar/calendar_next.rb +40 -0
  68. data/app/components/phlex_kit/calendar/calendar_prev.rb +40 -0
  69. data/app/components/phlex_kit/calendar/calendar_title.rb +19 -0
  70. data/app/components/phlex_kit/calendar/calendar_weekdays.rb +27 -0
  71. data/app/components/phlex_kit/card/card.css +15 -0
  72. data/app/components/phlex_kit/card/card.rb +29 -0
  73. data/app/components/phlex_kit/card/card_content.rb +12 -0
  74. data/app/components/phlex_kit/card/card_description.rb +12 -0
  75. data/app/components/phlex_kit/card/card_footer.rb +12 -0
  76. data/app/components/phlex_kit/card/card_header.rb +12 -0
  77. data/app/components/phlex_kit/card/card_title.rb +12 -0
  78. data/app/components/phlex_kit/carousel/carousel.css +41 -0
  79. data/app/components/phlex_kit/carousel/carousel.rb +37 -0
  80. data/app/components/phlex_kit/carousel/carousel_content.rb +16 -0
  81. data/app/components/phlex_kit/carousel/carousel_item.rb +17 -0
  82. data/app/components/phlex_kit/carousel/carousel_next.rb +39 -0
  83. data/app/components/phlex_kit/carousel/carousel_previous.rb +40 -0
  84. data/app/components/phlex_kit/chart/chart.css +9 -0
  85. data/app/components/phlex_kit/chart/chart.rb +31 -0
  86. data/app/components/phlex_kit/checkbox/checkbox.css +27 -0
  87. data/app/components/phlex_kit/checkbox/checkbox.rb +26 -0
  88. data/app/components/phlex_kit/clipboard/clipboard.css +8 -0
  89. data/app/components/phlex_kit/clipboard/clipboard.rb +19 -0
  90. data/app/components/phlex_kit/clipboard/clipboard_popover.rb +14 -0
  91. data/app/components/phlex_kit/clipboard/clipboard_source.rb +6 -0
  92. data/app/components/phlex_kit/clipboard/clipboard_trigger.rb +6 -0
  93. data/app/components/phlex_kit/codeblock/codeblock.css +7 -0
  94. data/app/components/phlex_kit/codeblock/codeblock.rb +23 -0
  95. data/app/components/phlex_kit/collapsible/collapsible.css +3 -0
  96. data/app/components/phlex_kit/collapsible/collapsible.rb +13 -0
  97. data/app/components/phlex_kit/collapsible/collapsible_content.rb +8 -0
  98. data/app/components/phlex_kit/collapsible/collapsible_trigger.rb +8 -0
  99. data/app/components/phlex_kit/combobox/combobox.css +310 -0
  100. data/app/components/phlex_kit/combobox/combobox.rb +33 -0
  101. data/app/components/phlex_kit/combobox/combobox_badge.rb +15 -0
  102. data/app/components/phlex_kit/combobox/combobox_badge_trigger.rb +55 -0
  103. data/app/components/phlex_kit/combobox/combobox_checkbox.rb +21 -0
  104. data/app/components/phlex_kit/combobox/combobox_clear_button.rb +39 -0
  105. data/app/components/phlex_kit/combobox/combobox_empty_state.rb +17 -0
  106. data/app/components/phlex_kit/combobox/combobox_input_trigger.rb +69 -0
  107. data/app/components/phlex_kit/combobox/combobox_item.rb +19 -0
  108. data/app/components/phlex_kit/combobox/combobox_item_indicator.rb +27 -0
  109. data/app/components/phlex_kit/combobox/combobox_list.rb +12 -0
  110. data/app/components/phlex_kit/combobox/combobox_list_group.rb +14 -0
  111. data/app/components/phlex_kit/combobox/combobox_popover.rb +27 -0
  112. data/app/components/phlex_kit/combobox/combobox_radio.rb +26 -0
  113. data/app/components/phlex_kit/combobox/combobox_search_input.rb +49 -0
  114. data/app/components/phlex_kit/combobox/combobox_toggle_all_checkbox.rb +21 -0
  115. data/app/components/phlex_kit/combobox/combobox_trigger.rb +48 -0
  116. data/app/components/phlex_kit/command/command.css +104 -0
  117. data/app/components/phlex_kit/command/command.rb +18 -0
  118. data/app/components/phlex_kit/command/command_dialog.rb +19 -0
  119. data/app/components/phlex_kit/command/command_dialog_content.rb +37 -0
  120. data/app/components/phlex_kit/command/command_dialog_trigger.rb +22 -0
  121. data/app/components/phlex_kit/command/command_empty.rb +17 -0
  122. data/app/components/phlex_kit/command/command_group.rb +32 -0
  123. data/app/components/phlex_kit/command/command_input.rb +56 -0
  124. data/app/components/phlex_kit/command/command_item.rb +22 -0
  125. data/app/components/phlex_kit/command/command_list.rb +12 -0
  126. data/app/components/phlex_kit/context_menu/context_menu.css +19 -0
  127. data/app/components/phlex_kit/context_menu/context_menu.rb +11 -0
  128. data/app/components/phlex_kit/context_menu/context_menu_content.rb +8 -0
  129. data/app/components/phlex_kit/context_menu/context_menu_item.rb +25 -0
  130. data/app/components/phlex_kit/context_menu/context_menu_label.rb +11 -0
  131. data/app/components/phlex_kit/context_menu/context_menu_separator.rb +8 -0
  132. data/app/components/phlex_kit/context_menu/context_menu_trigger.rb +8 -0
  133. data/app/components/phlex_kit/data_table/data_table.css +110 -0
  134. data/app/components/phlex_kit/data_table/data_table.rb +25 -0
  135. data/app/components/phlex_kit/data_table/data_table_bulk_actions.rb +15 -0
  136. data/app/components/phlex_kit/data_table/data_table_column_toggle.rb +61 -0
  137. data/app/components/phlex_kit/data_table/data_table_expand_toggle.rb +40 -0
  138. data/app/components/phlex_kit/data_table/data_table_form.rb +36 -0
  139. data/app/components/phlex_kit/data_table/data_table_kaminari_adapter.rb +17 -0
  140. data/app/components/phlex_kit/data_table/data_table_manual_adapter.rb +18 -0
  141. data/app/components/phlex_kit/data_table/data_table_pagination.rb +98 -0
  142. data/app/components/phlex_kit/data_table/data_table_pagination_bar.rb +13 -0
  143. data/app/components/phlex_kit/data_table/data_table_pagy_adapter.rb +17 -0
  144. data/app/components/phlex_kit/data_table/data_table_per_page_select.rb +29 -0
  145. data/app/components/phlex_kit/data_table/data_table_row_checkbox.rb +24 -0
  146. data/app/components/phlex_kit/data_table/data_table_search.rb +51 -0
  147. data/app/components/phlex_kit/data_table/data_table_select_all_checkbox.rb +19 -0
  148. data/app/components/phlex_kit/data_table/data_table_selection_summary.rb +19 -0
  149. data/app/components/phlex_kit/data_table/data_table_sort_head.rb +82 -0
  150. data/app/components/phlex_kit/data_table/data_table_toolbar.rb +13 -0
  151. data/app/components/phlex_kit/date_picker/date_picker.css +28 -0
  152. data/app/components/phlex_kit/date_picker/date_picker.rb +71 -0
  153. data/app/components/phlex_kit/dialog/dialog.css +32 -0
  154. data/app/components/phlex_kit/dialog/dialog.rb +14 -0
  155. data/app/components/phlex_kit/dialog/dialog_content.rb +21 -0
  156. data/app/components/phlex_kit/dialog/dialog_description.rb +6 -0
  157. data/app/components/phlex_kit/dialog/dialog_footer.rb +6 -0
  158. data/app/components/phlex_kit/dialog/dialog_header.rb +6 -0
  159. data/app/components/phlex_kit/dialog/dialog_middle.rb +6 -0
  160. data/app/components/phlex_kit/dialog/dialog_title.rb +6 -0
  161. data/app/components/phlex_kit/dialog/dialog_trigger.rb +8 -0
  162. data/app/components/phlex_kit/drawer/drawer.css +54 -0
  163. data/app/components/phlex_kit/drawer/drawer.rb +18 -0
  164. data/app/components/phlex_kit/drawer/drawer_close.rb +9 -0
  165. data/app/components/phlex_kit/drawer/drawer_content.rb +21 -0
  166. data/app/components/phlex_kit/drawer/drawer_description.rb +7 -0
  167. data/app/components/phlex_kit/drawer/drawer_footer.rb +7 -0
  168. data/app/components/phlex_kit/drawer/drawer_header.rb +7 -0
  169. data/app/components/phlex_kit/drawer/drawer_title.rb +7 -0
  170. data/app/components/phlex_kit/drawer/drawer_trigger.rb +9 -0
  171. data/app/components/phlex_kit/dropdown_menu/dropdown_menu.css +38 -0
  172. data/app/components/phlex_kit/dropdown_menu/dropdown_menu.rb +22 -0
  173. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_content.rb +23 -0
  174. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_item.rb +22 -0
  175. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_label.rb +12 -0
  176. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_separator.rb +12 -0
  177. data/app/components/phlex_kit/dropdown_menu/dropdown_menu_trigger.rb +15 -0
  178. data/app/components/phlex_kit/empty/empty.css +25 -0
  179. data/app/components/phlex_kit/empty/empty.rb +6 -0
  180. data/app/components/phlex_kit/empty/empty_content.rb +6 -0
  181. data/app/components/phlex_kit/empty/empty_description.rb +6 -0
  182. data/app/components/phlex_kit/empty/empty_header.rb +6 -0
  183. data/app/components/phlex_kit/empty/empty_media.rb +14 -0
  184. data/app/components/phlex_kit/empty/empty_title.rb +6 -0
  185. data/app/components/phlex_kit/form/form.css +15 -0
  186. data/app/components/phlex_kit/form/form.rb +27 -0
  187. data/app/components/phlex_kit/form_field/form_field.css +31 -0
  188. data/app/components/phlex_kit/form_field/form_field.rb +31 -0
  189. data/app/components/phlex_kit/form_field/form_field_error.rb +19 -0
  190. data/app/components/phlex_kit/form_field/form_field_hint.rb +13 -0
  191. data/app/components/phlex_kit/form_field/form_field_label.rb +13 -0
  192. data/app/components/phlex_kit/hover_card/hover_card.css +8 -0
  193. data/app/components/phlex_kit/hover_card/hover_card.rb +10 -0
  194. data/app/components/phlex_kit/hover_card/hover_card_content.rb +8 -0
  195. data/app/components/phlex_kit/hover_card/hover_card_trigger.rb +6 -0
  196. data/app/components/phlex_kit/input/input.css +29 -0
  197. data/app/components/phlex_kit/input/input.rb +34 -0
  198. data/app/components/phlex_kit/input_group/input_group.css +35 -0
  199. data/app/components/phlex_kit/input_group/input_group.rb +15 -0
  200. data/app/components/phlex_kit/input_group/input_group_addon.rb +16 -0
  201. data/app/components/phlex_kit/input_group/input_group_text.rb +7 -0
  202. data/app/components/phlex_kit/input_otp/input_otp.css +32 -0
  203. data/app/components/phlex_kit/input_otp/input_otp.rb +29 -0
  204. data/app/components/phlex_kit/input_otp/input_otp_group.rb +7 -0
  205. data/app/components/phlex_kit/input_otp/input_otp_separator.rb +7 -0
  206. data/app/components/phlex_kit/input_otp/input_otp_slot.rb +27 -0
  207. data/app/components/phlex_kit/item/item.css +32 -0
  208. data/app/components/phlex_kit/item/item.rb +18 -0
  209. data/app/components/phlex_kit/item/item_actions.rb +7 -0
  210. data/app/components/phlex_kit/item/item_content.rb +7 -0
  211. data/app/components/phlex_kit/item/item_description.rb +7 -0
  212. data/app/components/phlex_kit/item/item_group.rb +7 -0
  213. data/app/components/phlex_kit/item/item_media.rb +7 -0
  214. data/app/components/phlex_kit/item/item_title.rb +7 -0
  215. data/app/components/phlex_kit/kbd/kbd.css +17 -0
  216. data/app/components/phlex_kit/kbd/kbd.rb +14 -0
  217. data/app/components/phlex_kit/kbd/kbd_group.rb +12 -0
  218. data/app/components/phlex_kit/label/label.css +12 -0
  219. data/app/components/phlex_kit/label/label.rb +14 -0
  220. data/app/components/phlex_kit/link/link.css +6 -0
  221. data/app/components/phlex_kit/link/link.rb +47 -0
  222. data/app/components/phlex_kit/masked_input/masked_input.rb +12 -0
  223. data/app/components/phlex_kit/menubar/menubar.css +66 -0
  224. data/app/components/phlex_kit/menubar/menubar.rb +24 -0
  225. data/app/components/phlex_kit/menubar/menubar_content.rb +9 -0
  226. data/app/components/phlex_kit/menubar/menubar_item.rb +26 -0
  227. data/app/components/phlex_kit/menubar/menubar_menu.rb +9 -0
  228. data/app/components/phlex_kit/menubar/menubar_separator.rb +7 -0
  229. data/app/components/phlex_kit/menubar/menubar_trigger.rb +14 -0
  230. data/app/components/phlex_kit/message/message.css +20 -0
  231. data/app/components/phlex_kit/message/message.rb +14 -0
  232. data/app/components/phlex_kit/message/message_avatar.rb +6 -0
  233. data/app/components/phlex_kit/message/message_content.rb +6 -0
  234. data/app/components/phlex_kit/message/message_footer.rb +6 -0
  235. data/app/components/phlex_kit/message/message_group.rb +6 -0
  236. data/app/components/phlex_kit/message/message_header.rb +6 -0
  237. data/app/components/phlex_kit/message_scroller/message_scroller.css +2 -0
  238. data/app/components/phlex_kit/message_scroller/message_scroller.rb +11 -0
  239. data/app/components/phlex_kit/native_select/native_select.css +51 -0
  240. data/app/components/phlex_kit/native_select/native_select.rb +48 -0
  241. data/app/components/phlex_kit/native_select/native_select_group.rb +13 -0
  242. data/app/components/phlex_kit/native_select/native_select_icon.rb +32 -0
  243. data/app/components/phlex_kit/native_select/native_select_option.rb +14 -0
  244. data/app/components/phlex_kit/navigation_menu/navigation_menu.css +67 -0
  245. data/app/components/phlex_kit/navigation_menu/navigation_menu.rb +23 -0
  246. data/app/components/phlex_kit/navigation_menu/navigation_menu_content.rb +9 -0
  247. data/app/components/phlex_kit/navigation_menu/navigation_menu_item.rb +9 -0
  248. data/app/components/phlex_kit/navigation_menu/navigation_menu_link.rb +13 -0
  249. data/app/components/phlex_kit/navigation_menu/navigation_menu_list.rb +7 -0
  250. data/app/components/phlex_kit/navigation_menu/navigation_menu_trigger.rb +27 -0
  251. data/app/components/phlex_kit/pagination/pagination.css +5 -0
  252. data/app/components/phlex_kit/pagination/pagination.rb +10 -0
  253. data/app/components/phlex_kit/pagination/pagination_content.rb +6 -0
  254. data/app/components/phlex_kit/pagination/pagination_ellipsis.rb +17 -0
  255. data/app/components/phlex_kit/pagination/pagination_item.rb +14 -0
  256. data/app/components/phlex_kit/popover/popover.css +9 -0
  257. data/app/components/phlex_kit/popover/popover.rb +11 -0
  258. data/app/components/phlex_kit/popover/popover_content.rb +8 -0
  259. data/app/components/phlex_kit/popover/popover_trigger.rb +8 -0
  260. data/app/components/phlex_kit/progress/progress.css +17 -0
  261. data/app/components/phlex_kit/progress/progress.rb +25 -0
  262. data/app/components/phlex_kit/radio_button/radio_button.css +9 -0
  263. data/app/components/phlex_kit/radio_button/radio_button.rb +19 -0
  264. data/app/components/phlex_kit/radio_group/radio_group.css +3 -0
  265. data/app/components/phlex_kit/radio_group/radio_group.rb +14 -0
  266. data/app/components/phlex_kit/resizable/resizable.css +23 -0
  267. data/app/components/phlex_kit/resizable/resizable_handle.rb +21 -0
  268. data/app/components/phlex_kit/resizable/resizable_panel.rb +19 -0
  269. data/app/components/phlex_kit/resizable/resizable_panel_group.rb +26 -0
  270. data/app/components/phlex_kit/scroll_area/scroll_area.css +21 -0
  271. data/app/components/phlex_kit/scroll_area/scroll_area.rb +15 -0
  272. data/app/components/phlex_kit/select/select.css +80 -0
  273. data/app/components/phlex_kit/select/select.rb +43 -0
  274. data/app/components/phlex_kit/select/select_content.rb +24 -0
  275. data/app/components/phlex_kit/select/select_group.rb +13 -0
  276. data/app/components/phlex_kit/select/select_input.rb +23 -0
  277. data/app/components/phlex_kit/select/select_item.rb +52 -0
  278. data/app/components/phlex_kit/select/select_label.rb +13 -0
  279. data/app/components/phlex_kit/select/select_trigger.rb +42 -0
  280. data/app/components/phlex_kit/select/select_value.rb +21 -0
  281. data/app/components/phlex_kit/separator/separator.css +6 -0
  282. data/app/components/phlex_kit/separator/separator.rb +30 -0
  283. data/app/components/phlex_kit/sheet/sheet.css +17 -0
  284. data/app/components/phlex_kit/sheet/sheet.rb +14 -0
  285. data/app/components/phlex_kit/sheet/sheet_content.rb +25 -0
  286. data/app/components/phlex_kit/sheet/sheet_description.rb +6 -0
  287. data/app/components/phlex_kit/sheet/sheet_footer.rb +6 -0
  288. data/app/components/phlex_kit/sheet/sheet_header.rb +6 -0
  289. data/app/components/phlex_kit/sheet/sheet_middle.rb +6 -0
  290. data/app/components/phlex_kit/sheet/sheet_title.rb +6 -0
  291. data/app/components/phlex_kit/sheet/sheet_trigger.rb +6 -0
  292. data/app/components/phlex_kit/shortcut_key/shortcut_key.css +17 -0
  293. data/app/components/phlex_kit/shortcut_key/shortcut_key.rb +9 -0
  294. data/app/components/phlex_kit/sidebar/sidebar.css +42 -0
  295. data/app/components/phlex_kit/sidebar/sidebar.rb +19 -0
  296. data/app/components/phlex_kit/sidebar/sidebar_content.rb +12 -0
  297. data/app/components/phlex_kit/sidebar/sidebar_footer.rb +12 -0
  298. data/app/components/phlex_kit/sidebar/sidebar_group.rb +12 -0
  299. data/app/components/phlex_kit/sidebar/sidebar_header.rb +12 -0
  300. data/app/components/phlex_kit/sidebar/sidebar_inset.rb +14 -0
  301. data/app/components/phlex_kit/sidebar/sidebar_menu.rb +12 -0
  302. data/app/components/phlex_kit/sidebar/sidebar_menu_button.rb +17 -0
  303. data/app/components/phlex_kit/sidebar/sidebar_menu_item.rb +12 -0
  304. data/app/components/phlex_kit/sidebar/sidebar_wrapper.rb +13 -0
  305. data/app/components/phlex_kit/skeleton/skeleton.css +7 -0
  306. data/app/components/phlex_kit/skeleton/skeleton.rb +9 -0
  307. data/app/components/phlex_kit/slider/slider.css +52 -0
  308. data/app/components/phlex_kit/slider/slider.rb +39 -0
  309. data/app/components/phlex_kit/spinner/spinner.css +5 -0
  310. data/app/components/phlex_kit/spinner/spinner.rb +27 -0
  311. data/app/components/phlex_kit/stars/stars.css +4 -0
  312. data/app/components/phlex_kit/stars/stars.rb +19 -0
  313. data/app/components/phlex_kit/switch/switch.css +28 -0
  314. data/app/components/phlex_kit/switch/switch.rb +21 -0
  315. data/app/components/phlex_kit/table/table.css +24 -0
  316. data/app/components/phlex_kit/table/table.rb +35 -0
  317. data/app/components/phlex_kit/table/table_body.rb +12 -0
  318. data/app/components/phlex_kit/table/table_caption.rb +12 -0
  319. data/app/components/phlex_kit/table/table_cell.rb +12 -0
  320. data/app/components/phlex_kit/table/table_footer.rb +12 -0
  321. data/app/components/phlex_kit/table/table_head.rb +12 -0
  322. data/app/components/phlex_kit/table/table_header.rb +12 -0
  323. data/app/components/phlex_kit/table/table_row.rb +12 -0
  324. data/app/components/phlex_kit/tabs/tabs.css +13 -0
  325. data/app/components/phlex_kit/tabs/tabs.rb +13 -0
  326. data/app/components/phlex_kit/tabs/tabs_content.rb +11 -0
  327. data/app/components/phlex_kit/tabs/tabs_list.rb +6 -0
  328. data/app/components/phlex_kit/tabs/tabs_trigger.rb +17 -0
  329. data/app/components/phlex_kit/textarea/textarea.css +27 -0
  330. data/app/components/phlex_kit/textarea/textarea.rb +24 -0
  331. data/app/components/phlex_kit/theme_toggle/theme_toggle.rb +15 -0
  332. data/app/components/phlex_kit/toast/toast.css +163 -0
  333. data/app/components/phlex_kit/toast/toast.rb +21 -0
  334. data/app/components/phlex_kit/toast/toast_action.rb +19 -0
  335. data/app/components/phlex_kit/toast/toast_cancel.rb +18 -0
  336. data/app/components/phlex_kit/toast/toast_close.rb +35 -0
  337. data/app/components/phlex_kit/toast/toast_description.rb +13 -0
  338. data/app/components/phlex_kit/toast/toast_icon.rb +63 -0
  339. data/app/components/phlex_kit/toast/toast_item.rb +70 -0
  340. data/app/components/phlex_kit/toast/toast_region.rb +121 -0
  341. data/app/components/phlex_kit/toast/toast_title.rb +13 -0
  342. data/app/components/phlex_kit/toggle/toggle.css +16 -0
  343. data/app/components/phlex_kit/toggle/toggle.rb +59 -0
  344. data/app/components/phlex_kit/toggle_group/toggle_group.css +10 -0
  345. data/app/components/phlex_kit/toggle_group/toggle_group.rb +65 -0
  346. data/app/components/phlex_kit/toggle_group/toggle_group_item.rb +37 -0
  347. data/app/components/phlex_kit/tooltip/tooltip.css +28 -0
  348. data/app/components/phlex_kit/tooltip/tooltip.rb +15 -0
  349. data/app/components/phlex_kit/tooltip/tooltip_content.rb +12 -0
  350. data/app/components/phlex_kit/tooltip/tooltip_trigger.rb +13 -0
  351. data/app/components/phlex_kit/typography/blockquote.rb +12 -0
  352. data/app/components/phlex_kit/typography/heading.rb +38 -0
  353. data/app/components/phlex_kit/typography/inline_code.rb +13 -0
  354. data/app/components/phlex_kit/typography/inline_link.rb +15 -0
  355. data/app/components/phlex_kit/typography/text.rb +48 -0
  356. data/app/components/phlex_kit/typography/typography.css +50 -0
  357. data/app/javascript/phlex_kit/controllers/accordion_controller.js +59 -0
  358. data/app/javascript/phlex_kit/controllers/alert_dialog_controller.js +24 -0
  359. data/app/javascript/phlex_kit/controllers/avatar_controller.js +30 -0
  360. data/app/javascript/phlex_kit/controllers/calendar_controller.js +316 -0
  361. data/app/javascript/phlex_kit/controllers/calendar_input_controller.js +10 -0
  362. data/app/javascript/phlex_kit/controllers/carousel_controller.js +189 -0
  363. data/app/javascript/phlex_kit/controllers/chart_controller.js +135 -0
  364. data/app/javascript/phlex_kit/controllers/clipboard_controller.js +30 -0
  365. data/app/javascript/phlex_kit/controllers/collapsible_controller.js +14 -0
  366. data/app/javascript/phlex_kit/controllers/combobox_controller.js +303 -0
  367. data/app/javascript/phlex_kit/controllers/command_controller.js +161 -0
  368. data/app/javascript/phlex_kit/controllers/command_dialog_controller.js +37 -0
  369. data/app/javascript/phlex_kit/controllers/context_menu_controller.js +28 -0
  370. data/app/javascript/phlex_kit/controllers/data_table_column_visibility_controller.js +18 -0
  371. data/app/javascript/phlex_kit/controllers/data_table_controller.js +61 -0
  372. data/app/javascript/phlex_kit/controllers/data_table_search_controller.js +67 -0
  373. data/app/javascript/phlex_kit/controllers/dialog_controller.js +20 -0
  374. data/app/javascript/phlex_kit/controllers/dropdown_menu_controller.js +106 -0
  375. data/app/javascript/phlex_kit/controllers/form_field_controller.js +69 -0
  376. data/app/javascript/phlex_kit/controllers/hover_card_controller.js +11 -0
  377. data/app/javascript/phlex_kit/controllers/index.js +87 -0
  378. data/app/javascript/phlex_kit/controllers/input_otp_controller.js +66 -0
  379. data/app/javascript/phlex_kit/controllers/masked_input_controller.js +26 -0
  380. data/app/javascript/phlex_kit/controllers/menubar_controller.js +43 -0
  381. data/app/javascript/phlex_kit/controllers/message_scroller_controller.js +335 -0
  382. data/app/javascript/phlex_kit/controllers/popover_controller.js +12 -0
  383. data/app/javascript/phlex_kit/controllers/resizable_controller.js +42 -0
  384. data/app/javascript/phlex_kit/controllers/select_controller.js +141 -0
  385. data/app/javascript/phlex_kit/controllers/select_item_controller.js +14 -0
  386. data/app/javascript/phlex_kit/controllers/sheet_content_controller.js +6 -0
  387. data/app/javascript/phlex_kit/controllers/sheet_controller.js +10 -0
  388. data/app/javascript/phlex_kit/controllers/slider_controller.js +19 -0
  389. data/app/javascript/phlex_kit/controllers/tabs_controller.js +28 -0
  390. data/app/javascript/phlex_kit/controllers/theme_toggle_controller.js +22 -0
  391. data/app/javascript/phlex_kit/controllers/toast_controller.js +153 -0
  392. data/app/javascript/phlex_kit/controllers/toaster_controller.js +321 -0
  393. data/app/javascript/phlex_kit/controllers/toggle_controller.js +25 -0
  394. data/app/javascript/phlex_kit/controllers/toggle_group_controller.js +91 -0
  395. data/config/importmap.rb +7 -0
  396. data/lib/generators/phlex_kit/component/component_generator.rb +41 -0
  397. data/lib/generators/phlex_kit/install/install_generator.rb +51 -0
  398. data/lib/generators/phlex_kit/install/templates/phlex_kit.rb +11 -0
  399. data/lib/phlex_kit/base_component.rb +22 -0
  400. data/lib/phlex_kit/configuration.rb +46 -0
  401. data/lib/phlex_kit/engine.rb +50 -0
  402. data/lib/phlex_kit/propshaft_skip_source.rb +22 -0
  403. data/lib/phlex_kit/version.rb +5 -0
  404. data/lib/phlex_kit.rb +21 -0
  405. metadata +545 -0
@@ -0,0 +1,30 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Ported verbatim from ruby_ui's phlex-kit--avatar controller. Shows the image once
4
+ // it has loaded (else the fallback). With no image target (fallback-only avatar)
5
+ // connect() no-ops and the fallback stays visible.
6
+ export default class extends Controller {
7
+ static targets = ["image", "fallback"];
8
+
9
+ connect() {
10
+ if (!this.hasImageTarget) {
11
+ return;
12
+ }
13
+
14
+ if (this.imageTarget.complete && this.imageTarget.naturalWidth > 0) {
15
+ this.showImage();
16
+ } else {
17
+ this.showFallback();
18
+ }
19
+ }
20
+
21
+ showImage() {
22
+ this.imageTargets.forEach((image) => image.classList.remove("hidden"));
23
+ this.fallbackTargets.forEach((fallback) => fallback.classList.add("hidden"));
24
+ }
25
+
26
+ showFallback() {
27
+ this.imageTargets.forEach((image) => image.classList.add("hidden"));
28
+ this.fallbackTargets.forEach((fallback) => fallback.classList.remove("hidden"));
29
+ }
30
+ }
@@ -0,0 +1,316 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Ported from ruby_ui's calendar controller with the mustache dependency
4
+ // replaced: the day templates only use {{day}} / {{dayDate}}, so this tiny
5
+ // interpolator covers them and only @hotwired/stimulus is needed. Identifiers
6
+ // renamed ruby-ui -> phlex-kit; the week-row markup uses .pk-calendar-week
7
+ // (calendar.css) instead of Tailwind utilities.
8
+ function renderTemplate(template, data) {
9
+ return template.replace(/{{\s*(\w+)\s*}}/g, (_, key) => String(data[key] ?? ""));
10
+ }
11
+
12
+ export default class extends Controller {
13
+ static targets = [
14
+ "calendar",
15
+ "title",
16
+ "weekdaysTemplate",
17
+ "disabledDateTemplate",
18
+ "selectedDateTemplate",
19
+ "todayDateTemplate",
20
+ "currentMonthDateTemplate",
21
+ "otherMonthDateTemplate",
22
+ ];
23
+ static values = {
24
+ selectedDate: {
25
+ type: String,
26
+ default: null,
27
+ },
28
+ minDate: {
29
+ type: String,
30
+ default: null,
31
+ },
32
+ viewDate: {
33
+ type: String,
34
+ default: new Date().toISOString().slice(0, 10),
35
+ },
36
+ format: {
37
+ type: String,
38
+ default: "yyyy-MM-dd", // Default format
39
+ },
40
+ };
41
+ static outlets = ["phlex-kit--calendar-input"];
42
+
43
+ initialize() {
44
+ this.updateCalendar(); // Initial calendar render
45
+ }
46
+
47
+ nextMonth(e) {
48
+ e.preventDefault();
49
+ this.viewDateValue = this.adjustMonth(1);
50
+ }
51
+
52
+ prevMonth(e) {
53
+ e.preventDefault();
54
+ this.viewDateValue = this.adjustMonth(-1);
55
+ }
56
+
57
+ selectDay(e) {
58
+ e.preventDefault();
59
+ if (this.isDateDisabled(e.currentTarget.dataset.day)) return;
60
+
61
+ // Set the selected date value
62
+ this.selectedDateValue = e.currentTarget.dataset.day;
63
+ }
64
+
65
+ selectedDateValueChanged(value, prevValue) {
66
+ const selectedDate = this.selectedDate();
67
+ if (!selectedDate) {
68
+ this.updateCalendar();
69
+ return;
70
+ }
71
+
72
+ // update the viewDateValue to the first day of month of the selected date (This will trigger updateCalendar() function)
73
+ const newViewDate = new Date(selectedDate);
74
+ newViewDate.setDate(2); // set the day to the 2nd (to avoid issues with months with different number of days and timezones)
75
+ this.viewDateValue = newViewDate.toISOString().slice(0, 10);
76
+
77
+ // Re-render the calendar
78
+ this.updateCalendar();
79
+
80
+ // update the input value
81
+ this.phlexKitCalendarInputOutlets.forEach((outlet) => {
82
+ const formattedDate = this.formatDate(selectedDate);
83
+ outlet.setValue(formattedDate);
84
+ });
85
+ }
86
+
87
+ viewDateValueChanged(value, prevValue) {
88
+ this.updateCalendar();
89
+ }
90
+
91
+ adjustMonth(adjustment) {
92
+ const date = this.viewDate();
93
+ date.setDate(2); // set the day to the 2nd (to avoid issues with months with different number of days and timezones)
94
+ date.setMonth(date.getMonth() + adjustment);
95
+ return date.toISOString().slice(0, 10);
96
+ }
97
+
98
+ updateCalendar() {
99
+ // Update the title with month and year
100
+ this.titleTarget.textContent = this.monthAndYear();
101
+ this.calendarTarget.innerHTML = this.calendarHTML();
102
+ }
103
+
104
+ calendarHTML() {
105
+ return this.weekdaysTemplateTarget.innerHTML + this.calendarDays();
106
+ }
107
+
108
+ calendarDays() {
109
+ return this.getFullWeeksStartAndEndInMonth()
110
+ .map((week) => this.renderWeek(week))
111
+ .join("");
112
+ }
113
+
114
+ renderWeek(week) {
115
+ const days = week
116
+ .map((day) => {
117
+ return this.renderDay(day);
118
+ })
119
+ .join("");
120
+ return `<tr class="pk-calendar-week">${days}</tr>`;
121
+ }
122
+
123
+ renderDay(day) {
124
+ const today = new Date();
125
+ const selectedDate = this.selectedDate();
126
+ let dateHTML = "";
127
+ const data = { day: day, dayDate: day.getDate() };
128
+
129
+ if (this.isDateDisabled(day)) {
130
+ // disabledDate
131
+ dateHTML = renderTemplate(
132
+ this.disabledDateTemplateTarget.innerHTML,
133
+ data,
134
+ );
135
+ } else if (
136
+ selectedDate &&
137
+ day.toDateString() === selectedDate.toDateString()
138
+ ) {
139
+ // selectedDate
140
+ // Render the selected date template
141
+ dateHTML = renderTemplate(
142
+ this.selectedDateTemplateTarget.innerHTML,
143
+ data,
144
+ );
145
+ } else if (day.toDateString() === today.toDateString()) {
146
+ // todayDate
147
+ dateHTML = renderTemplate(this.todayDateTemplateTarget.innerHTML, data);
148
+ } else if (day.getMonth() === this.viewDate().getMonth()) {
149
+ // currentMonthDate
150
+ dateHTML = renderTemplate(
151
+ this.currentMonthDateTemplateTarget.innerHTML,
152
+ data,
153
+ );
154
+ } else {
155
+ // otherMonthDate
156
+ dateHTML = renderTemplate(
157
+ this.otherMonthDateTemplateTarget.innerHTML,
158
+ data,
159
+ );
160
+ }
161
+ return dateHTML;
162
+ }
163
+
164
+ monthAndYear() {
165
+ const month = this.viewDate().toLocaleString("en-US", { month: "long" });
166
+ const year = this.viewDate().getFullYear();
167
+ return `${month} ${year}`;
168
+ }
169
+
170
+ selectedDate() {
171
+ return this.parseDate(this.selectedDateValue);
172
+ }
173
+
174
+ viewDate() {
175
+ return (
176
+ this.parseDate(this.viewDateValue) || this.selectedDate() || new Date()
177
+ );
178
+ }
179
+
180
+ getFullWeeksStartAndEndInMonth() {
181
+ const month = this.viewDate().getMonth();
182
+ const year = this.viewDate().getFullYear();
183
+
184
+ let weeks = [],
185
+ firstDate = new Date(year, month, 1),
186
+ lastDate = new Date(year, month + 1, 0),
187
+ numDays = lastDate.getDate();
188
+
189
+ let start = 1;
190
+ let end;
191
+ if (firstDate.getDay() === 1) {
192
+ end = 7;
193
+ } else if (firstDate.getDay() === 0) {
194
+ let preMonthEndDay = new Date(year, month, 0);
195
+ start = preMonthEndDay.getDate() - 6 + 1;
196
+ end = 1;
197
+ } else {
198
+ let preMonthEndDay = new Date(year, month, 0);
199
+ start = preMonthEndDay.getDate() + 1 - firstDate.getDay() + 1;
200
+ end = 7 - firstDate.getDay() + 1;
201
+ weeks.push({
202
+ start: start,
203
+ end: end,
204
+ });
205
+ start = end + 1;
206
+ end = end + 7;
207
+ }
208
+ while (start <= numDays) {
209
+ weeks.push({
210
+ start: start,
211
+ end: end,
212
+ });
213
+ start = end + 1;
214
+ end = end + 7;
215
+ end = start === 1 && end === 8 ? 1 : end;
216
+ if (end > numDays && start <= numDays) {
217
+ end = end - numDays;
218
+ weeks.push({
219
+ start: start,
220
+ end: end,
221
+ });
222
+ break;
223
+ }
224
+ }
225
+ // *** the magic starts here
226
+ return weeks.map(({ start, end }, index) => {
227
+ const sub = +(start > end && index === 0);
228
+ return Array.from({ length: 7 }, (_, index) => {
229
+ const date = new Date(year, month - sub, start + index);
230
+ return date;
231
+ });
232
+ });
233
+ }
234
+
235
+ formatDate(date) {
236
+ const format = this.formatValue;
237
+ const day = date.getDate();
238
+ const month = date.getMonth() + 1;
239
+ const year = date.getFullYear();
240
+ const hours = date.getHours();
241
+ const minutes = date.getMinutes();
242
+ const seconds = date.getSeconds();
243
+ const dayOfWeek = date.toLocaleString("en-US", { weekday: "long" });
244
+ const monthName = date.toLocaleString("en-US", { month: "long" });
245
+ const daySuffix = this.getDaySuffix(day);
246
+
247
+ const map = {
248
+ yyyy: year,
249
+ MM: ("0" + month).slice(-2),
250
+ dd: ("0" + day).slice(-2),
251
+ HH: ("0" + hours).slice(-2),
252
+ mm: ("0" + minutes).slice(-2),
253
+ ss: ("0" + seconds).slice(-2),
254
+ EEEE: dayOfWeek,
255
+ MMMM: monthName,
256
+ do: day + daySuffix,
257
+ PPPP: `${dayOfWeek}, ${monthName} ${day}${daySuffix}, ${year}`,
258
+ };
259
+
260
+ const formattedDate = format.replace(
261
+ /yyyy|MM|dd|HH|mm|ss|EEEE|MMMM|do|PPPP/g,
262
+ (matched) => map[matched],
263
+ );
264
+ return formattedDate;
265
+ }
266
+
267
+ getDaySuffix(day) {
268
+ if (day > 3 && day < 21) return "th";
269
+ switch (day % 10) {
270
+ case 1:
271
+ return "st";
272
+ case 2:
273
+ return "nd";
274
+ case 3:
275
+ return "rd";
276
+ default:
277
+ return "th";
278
+ }
279
+ }
280
+
281
+ minDate() {
282
+ return this.parseDate(this.minDateValue);
283
+ }
284
+
285
+ isDateDisabled(date) {
286
+ const minDate = this.minDate();
287
+ const candidate = this.parseDate(date);
288
+
289
+ if (!minDate || !candidate) return false;
290
+
291
+ return this.startOfDay(candidate) < this.startOfDay(minDate);
292
+ }
293
+
294
+ parseDate(value) {
295
+ if (!value) return null;
296
+ if (value instanceof Date) return new Date(value);
297
+
298
+ const isoDate = value.toString().match(/^(\d{4})-(\d{2})-(\d{2})/);
299
+ if (isoDate) {
300
+ return new Date(
301
+ Number(isoDate[1]),
302
+ Number(isoDate[2]) - 1,
303
+ Number(isoDate[3]),
304
+ );
305
+ }
306
+
307
+ const date = new Date(value);
308
+ return Number.isNaN(date.getTime()) ? null : date;
309
+ }
310
+
311
+ startOfDay(date) {
312
+ const normalizedDate = new Date(date);
313
+ normalizedDate.setHours(0, 0, 0, 0);
314
+ return normalizedDate;
315
+ }
316
+ }
@@ -0,0 +1,10 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Ported from ruby_ui's calendar-input controller (Stimulus-only upstream).
4
+ // The calendar's outlet pushes the picked, formatted date into this element.
5
+ // Connects to data-controller="phlex-kit--calendar-input"
6
+ export default class extends Controller {
7
+ setValue(value) {
8
+ this.element.value = value
9
+ }
10
+ }
@@ -0,0 +1,189 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ const DEFAULT_OPTIONS = {
4
+ loop: true,
5
+ axis: "x",
6
+ };
7
+
8
+ // How far (fraction of a slide) or how fast (px/ms) a drag must travel to
9
+ // advance instead of snapping back — tuned to embla's feel.
10
+ const DRAG_DISTANCE_THRESHOLD = 0.25;
11
+ const DRAG_VELOCITY_THRESHOLD = 0.5;
12
+
13
+ // Ported from ruby_ui's carousel controller with the embla-carousel dependency
14
+ // removed: a minimal translate-based engine (scrollNext/scrollPrev, loop, x/y
15
+ // axis from the options value) moves the track directly, so only
16
+ // @hotwired/stimulus is needed. Button disabled state mirrors embla's
17
+ // canScrollNext/canScrollPrev, and pointer drag/swipe (threshold + velocity,
18
+ // rubber-band at the ends when not looping, click suppression after a drag)
19
+ // replaces embla's gesture engine.
20
+ export default class extends Controller {
21
+ static values = {
22
+ options: {
23
+ type: Object,
24
+ default: {},
25
+ },
26
+ };
27
+ static targets = ["viewport", "nextButton", "prevButton"];
28
+
29
+ connect() {
30
+ this.index = 0;
31
+ this.track = this.viewportTarget.firstElementChild;
32
+ this.drag = null;
33
+ this.suppressClick = false;
34
+ this._onResize = () => this._applyTransform();
35
+ this._onPointerDown = this._onPointerDown.bind(this);
36
+ this._onPointerMove = this._onPointerMove.bind(this);
37
+ this._onPointerUp = this._onPointerUp.bind(this);
38
+ this._onClickCapture = this._onClickCapture.bind(this);
39
+ window.addEventListener("resize", this._onResize);
40
+ this.viewportTarget.addEventListener("pointerdown", this._onPointerDown);
41
+ this.viewportTarget.addEventListener("click", this._onClickCapture, true);
42
+ this._update();
43
+ }
44
+
45
+ disconnect() {
46
+ window.removeEventListener("resize", this._onResize);
47
+ this.viewportTarget.removeEventListener("pointerdown", this._onPointerDown);
48
+ this.viewportTarget.removeEventListener("click", this._onClickCapture, true);
49
+ }
50
+
51
+ scrollNext() {
52
+ this._goTo(this.index + 1);
53
+ }
54
+
55
+ scrollPrev() {
56
+ this._goTo(this.index - 1);
57
+ }
58
+
59
+ get slides() {
60
+ return this.track ? Array.from(this.track.children) : [];
61
+ }
62
+
63
+ get options() {
64
+ return { ...DEFAULT_OPTIONS, ...this.optionsValue };
65
+ }
66
+
67
+ _goTo(index) {
68
+ const count = this.slides.length;
69
+ if (count === 0) return;
70
+ if (this.options.loop) {
71
+ index = ((index % count) + count) % count;
72
+ } else {
73
+ index = Math.max(0, Math.min(index, count - 1));
74
+ }
75
+ this.index = index;
76
+ this._update();
77
+ }
78
+
79
+ _update() {
80
+ this._applyTransform();
81
+ const last = this.slides.length - 1;
82
+ const canNext = this.options.loop ? this.slides.length > 1 : this.index < last;
83
+ const canPrev = this.options.loop ? this.slides.length > 1 : this.index > 0;
84
+ this.nextButtonTargets.forEach((button) => (button.disabled = !canNext));
85
+ this.prevButtonTargets.forEach((button) => (button.disabled = !canPrev));
86
+ }
87
+
88
+ _applyTransform() {
89
+ if (!this.slides[this.index]) return;
90
+ this._translateTo(this._offsetOf(this.index));
91
+ }
92
+
93
+ // --- drag/swipe ---
94
+
95
+ _onPointerDown(e) {
96
+ if (e.button !== 0 || this.slides.length < 2) return;
97
+ this.suppressClick = false;
98
+ this.drag = {
99
+ start: this._pointerPos(e),
100
+ startedAt: performance.now(),
101
+ offset: this._offsetOf(this.index),
102
+ delta: 0,
103
+ moved: false,
104
+ };
105
+ try { this.viewportTarget.setPointerCapture(e.pointerId); } catch {}
106
+ this.viewportTarget.addEventListener("pointermove", this._onPointerMove);
107
+ this.viewportTarget.addEventListener("pointerup", this._onPointerUp);
108
+ this.viewportTarget.addEventListener("pointercancel", this._onPointerUp);
109
+ }
110
+
111
+ _onPointerMove(e) {
112
+ const drag = this.drag;
113
+ if (!drag) return;
114
+ drag.delta = this._pointerPos(e) - drag.start;
115
+ if (!drag.moved && Math.abs(drag.delta) > 5) {
116
+ drag.moved = true;
117
+ this.track.style.transition = "none";
118
+ this.viewportTarget.classList.add("dragging");
119
+ }
120
+ if (!drag.moved) return;
121
+
122
+ let offset = drag.offset - drag.delta;
123
+ if (!this.options.loop) {
124
+ // rubber-band past the ends
125
+ const max = this._offsetOf(this.slides.length - 1);
126
+ if (offset < 0) offset = offset / 3;
127
+ if (offset > max) offset = max + (offset - max) / 3;
128
+ }
129
+ this._translateTo(offset);
130
+ }
131
+
132
+ _onPointerUp() {
133
+ const drag = this.drag;
134
+ this.drag = null;
135
+ this.viewportTarget.removeEventListener("pointermove", this._onPointerMove);
136
+ this.viewportTarget.removeEventListener("pointerup", this._onPointerUp);
137
+ this.viewportTarget.removeEventListener("pointercancel", this._onPointerUp);
138
+ if (!drag || !drag.moved) return;
139
+
140
+ this.track.style.transition = "";
141
+ this.viewportTarget.classList.remove("dragging");
142
+ this.suppressClick = true; // a drag must not activate links in the slide
143
+
144
+ const elapsed = Math.max(performance.now() - drag.startedAt, 1);
145
+ const velocity = Math.abs(drag.delta) / elapsed;
146
+ const passed = Math.abs(drag.delta) > this._slideSize() * DRAG_DISTANCE_THRESHOLD
147
+ || velocity > DRAG_VELOCITY_THRESHOLD;
148
+
149
+ if (passed) {
150
+ drag.delta < 0 ? this.scrollNext() : this.scrollPrev();
151
+ } else {
152
+ this._applyTransform(); // snap back
153
+ }
154
+ }
155
+
156
+ _onClickCapture(e) {
157
+ if (!this.suppressClick) return;
158
+ this.suppressClick = false;
159
+ e.preventDefault();
160
+ e.stopPropagation();
161
+ }
162
+
163
+ // --- geometry ---
164
+
165
+ _pointerPos(e) {
166
+ return this.options.axis === "y" ? e.clientY : e.clientX;
167
+ }
168
+
169
+ _offsetOf(index) {
170
+ const slide = this.slides[index];
171
+ const first = this.slides[0];
172
+ if (!slide || !first) return 0;
173
+ return this.options.axis === "y"
174
+ ? slide.offsetTop - first.offsetTop
175
+ : slide.offsetLeft - first.offsetLeft;
176
+ }
177
+
178
+ _slideSize() {
179
+ const slide = this.slides[this.index];
180
+ if (!slide) return 1;
181
+ return (this.options.axis === "y" ? slide.offsetHeight : slide.offsetWidth) || 1;
182
+ }
183
+
184
+ _translateTo(offset) {
185
+ this.track.style.transform = this.options.axis === "y"
186
+ ? `translate3d(0, ${-offset}px, 0)`
187
+ : `translate3d(${-offset}px, 0, 0)`;
188
+ }
189
+ }
@@ -0,0 +1,135 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Ported from ruby_ui's chart controller with the bundled chart.js dependency
4
+ // removed: if the host exposes chart.js as window.Chart the behaviour matches
5
+ // upstream (token-derived default colors — mapped to the --pk-* theme — plus a
6
+ // re-render on theme change); otherwise a "phlex-kit--chart:connect" event is
7
+ // dispatched with { canvas, options } so the host can drive any charting
8
+ // library it ships. Only @hotwired/stimulus is required.
9
+ // Connects to data-controller="phlex-kit--chart"
10
+ export default class extends Controller {
11
+ static values = {
12
+ options: {
13
+ type: Object,
14
+ default: {},
15
+ }
16
+ }
17
+
18
+ connect() {
19
+ if (this.chartLibrary()) {
20
+ this.initThemeObserver()
21
+ this.initChart()
22
+ } else {
23
+ // No window.Chart — hand the config to the host's own charting code.
24
+ this.dispatch("connect", { detail: { canvas: this.element, options: this.optionsValue } })
25
+ }
26
+ }
27
+
28
+ disconnect() {
29
+ this.themeObserver?.disconnect()
30
+ this.chart?.destroy()
31
+ if (!this.chart) {
32
+ this.dispatch("disconnect", { detail: { canvas: this.element } })
33
+ }
34
+ }
35
+
36
+ chartLibrary() {
37
+ return window.Chart
38
+ }
39
+
40
+ initChart() {
41
+ this.setDefaultColorsForChart()
42
+ const ctx = this.element.getContext("2d")
43
+ this.chart = new (this.chartLibrary())(ctx, this.mergeOptionsWithDefaults())
44
+ }
45
+
46
+ // Kit tokens are plain color values (hex / color-mix results), not the hsl
47
+ // triplets ruby_ui reads — return them as-is.
48
+ getThemeColor(name) {
49
+ return getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim()
50
+ }
51
+
52
+ // shadcn-style series colors: dataset N takes --pk-chart-(N%5 + 1). Line/area
53
+ // datasets get a translucent fill of the same hue; circular charts color each
54
+ // slice instead (below).
55
+ seriesColor(index) {
56
+ return this.getThemeColor(`pk-chart-${(index % 5) + 1}`)
57
+ }
58
+
59
+ defaultThemeColor(index) {
60
+ const color = this.seriesColor(index)
61
+ const translucentFill = ["line", "radar"].includes(this.optionsValue.type)
62
+ return {
63
+ backgroundColor: this.isCircular() ? undefined : (translucentFill ? color + "33" : color),
64
+ hoverBackgroundColor: color,
65
+ borderColor: color,
66
+ borderWidth: 2,
67
+ pointRadius: 0,
68
+ pointHoverRadius: 4,
69
+ tension: 0.4,
70
+ }
71
+ }
72
+
73
+ isCircular() {
74
+ return ["pie", "doughnut", "polarArea"].includes(this.optionsValue.type)
75
+ }
76
+
77
+ setDefaultColorsForChart() {
78
+ const Chart = this.chartLibrary()
79
+ Chart.defaults.color = this.getThemeColor("pk-muted") // font color
80
+ Chart.defaults.borderColor = this.getThemeColor("pk-border") // border color
81
+ Chart.defaults.backgroundColor = this.getThemeColor("pk-bg") // background color
82
+
83
+ // tooltip colors
84
+ Chart.defaults.plugins.tooltip.backgroundColor = this.getThemeColor("pk-surface")
85
+ Chart.defaults.plugins.tooltip.borderColor = this.getThemeColor("pk-border")
86
+ Chart.defaults.plugins.tooltip.titleColor = this.getThemeColor("pk-text")
87
+ Chart.defaults.plugins.tooltip.bodyColor = this.getThemeColor("pk-muted")
88
+ Chart.defaults.plugins.tooltip.borderWidth = 1
89
+
90
+ // legend
91
+ Chart.defaults.plugins.legend.labels.boxWidth = 12
92
+ Chart.defaults.plugins.legend.labels.boxHeight = 12
93
+ Chart.defaults.plugins.legend.labels.borderWidth = 0
94
+ Chart.defaults.plugins.legend.labels.useBorderRadius = true
95
+ Chart.defaults.plugins.legend.labels.borderRadius = 2
96
+
97
+ // shadcn look: hairline grid, no axis borders, muted ticks
98
+ Chart.defaults.scale.grid = { ...Chart.defaults.scale.grid, color: this.getThemeColor("pk-border"), drawTicks: false }
99
+ Chart.defaults.scale.border = { ...Chart.defaults.scale.border, display: false }
100
+ }
101
+
102
+ refreshChart() {
103
+ this.chart?.destroy()
104
+ this.initChart()
105
+ }
106
+
107
+ // The kit themes via <html data-theme="...">, not a class — watch that.
108
+ initThemeObserver() {
109
+ this.themeObserver = new MutationObserver(() => {
110
+ this.refreshChart()
111
+ })
112
+ this.themeObserver.observe(document.documentElement, { attributeFilter: ["data-theme", "class"] })
113
+ }
114
+
115
+ mergeOptionsWithDefaults() {
116
+ return {
117
+ ...this.optionsValue,
118
+ data: {
119
+ ...this.optionsValue.data,
120
+ datasets: (this.optionsValue.data?.datasets || []).map((dataset, index) => {
121
+ const defaults = this.defaultThemeColor(index)
122
+ if (this.isCircular()) {
123
+ // one dataset, one color per slice
124
+ defaults.backgroundColor = (dataset.data || []).map((_, i) => this.seriesColor(i))
125
+ defaults.borderColor = this.getThemeColor("pk-surface")
126
+ }
127
+ return {
128
+ ...defaults,
129
+ ...dataset,
130
+ }
131
+ })
132
+ }
133
+ }
134
+ }
135
+ }