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,121 @@
1
+ module PhlexKit
2
+ # Toast region ("toaster"), ported from ruby_ui's RubyUI::ToastRegion. Renders
3
+ # the fixed, position-anchored <ol> stack plus hidden <template> skeletons (one
4
+ # per variant) and action/cancel/close slot templates. The phlex-kit--toaster
5
+ # controller clones those to spawn toasts client-side — via
6
+ # `window.PhlexKit.toast(...)`, a "phlex-kit:toast" window event, or the `toast`
7
+ # Turbo Stream action. Pass `flash:` to render server-side flash messages as
8
+ # toasts on page load. Per-toast behaviour (timer, swipe) lives on each
9
+ # PhlexKit::ToastItem. Tailwind → vanilla `.pk-toast*` (toast.css).
10
+ class ToastRegion < BaseComponent
11
+ SKELETON_VARIANTS = %i[default success error warning info loading].freeze
12
+
13
+ POSITIONS = {
14
+ top_left: "top-left",
15
+ top_center: "top-center",
16
+ top_right: "top-right",
17
+ bottom_left: "bottom-left",
18
+ bottom_center: "bottom-center",
19
+ bottom_right: "bottom-right"
20
+ }.freeze
21
+
22
+ def initialize(
23
+ position: :bottom_right,
24
+ expand: false,
25
+ max: 3,
26
+ duration: 4000,
27
+ gap: 14,
28
+ offset: 24,
29
+ theme: :system,
30
+ rich_colors: false,
31
+ close_button: false,
32
+ hotkey: %w[alt t],
33
+ dir: :ltr,
34
+ flash: nil,
35
+ **attrs
36
+ )
37
+ @position = POSITIONS.fetch(position.to_sym)
38
+ @expand = expand
39
+ @max = max
40
+ @duration = duration
41
+ @gap = gap
42
+ @offset = offset
43
+ @theme = theme.to_sym
44
+ @rich_colors = rich_colors
45
+ @close_button = close_button
46
+ @hotkey = hotkey
47
+ @dir = dir
48
+ @flash = flash
49
+ @attrs = attrs
50
+ end
51
+
52
+ def view_template(&block)
53
+ div(**mix(region_attrs, @attrs)) do
54
+ ol(id: "pk-toaster", class: "pk-toast-list") do
55
+ render_flash if @flash
56
+ yield(self) if block
57
+ end
58
+ SKELETON_VARIANTS.each { |variant| skeleton(variant) }
59
+ slot_template("actionTpl") { render ToastAction.new(label: "") }
60
+ slot_template("cancelTpl") { render ToastCancel.new(label: "") }
61
+ slot_template("closeTpl") { render ToastClose.new }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def render_flash
68
+ @flash.each do |key, message|
69
+ next if message.nil? || message.to_s.empty?
70
+ variant = Toast.flash_variant(key)
71
+ render ToastItem.new(variant: variant, id: "flash-#{key}") do
72
+ render ToastIcon.new(variant: variant)
73
+ render ToastTitle.new { message.to_s }
74
+ end
75
+ end
76
+ end
77
+
78
+ def skeleton(variant)
79
+ template(data: { phlex_kit__toaster_target: "skeleton", variant: variant.to_s }) do
80
+ render ToastItem.new(variant: variant) do
81
+ render ToastIcon.new(variant: variant)
82
+ div(class: "pk-toast-body") do
83
+ render ToastTitle.new
84
+ render ToastDescription.new
85
+ end
86
+ render ToastClose.new if @close_button
87
+ end
88
+ end
89
+ end
90
+
91
+ def slot_template(target_name, &)
92
+ template(data: { phlex_kit__toaster_target: target_name }, &)
93
+ end
94
+
95
+ def region_attrs
96
+ {
97
+ id: "pk-toaster-region",
98
+ role: "region",
99
+ class: "pk-toast-region",
100
+ aria: { label: "Notifications", live: "polite" },
101
+ data: {
102
+ controller: "phlex-kit--toaster",
103
+ turbo_permanent: "",
104
+ close_button: @close_button.to_s,
105
+ position: @position,
106
+ phlex_kit__toaster_position_value: @position,
107
+ phlex_kit__toaster_expand_value: @expand.to_s,
108
+ phlex_kit__toaster_max_value: @max.to_s,
109
+ phlex_kit__toaster_duration_value: @duration.to_s,
110
+ phlex_kit__toaster_gap_value: @gap.to_s,
111
+ phlex_kit__toaster_offset_value: @offset.to_s,
112
+ phlex_kit__toaster_theme_value: @theme.to_s,
113
+ phlex_kit__toaster_rich_colors_value: @rich_colors.to_s,
114
+ phlex_kit__toaster_close_button_value: @close_button.to_s,
115
+ phlex_kit__toaster_hotkey_value: Array(@hotkey).join("+"),
116
+ phlex_kit__toaster_dir_value: @dir.to_s
117
+ }
118
+ }
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,13 @@
1
+ module PhlexKit
2
+ # Toast headline. The data-slot lets the toaster fill it when cloning a
3
+ # skeleton. See toast_region.rb.
4
+ class ToastTitle < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&)
10
+ div(**mix({ class: "pk-toast-title", data: { slot: "title" } }, @attrs), &)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ /* Co-located with toggle.rb. Theme tokens from the global stylesheet. */
2
+ .pk-contents { display: contents; }
3
+ .pk-toggle {
4
+ display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
5
+ height: 2.25rem; min-width: 2.25rem; padding-inline: .5rem;
6
+ border-radius: calc(var(--pk-radius) - 2px); border: 1px solid transparent; background: transparent;
7
+ font-size: .875rem; font-weight: 500; white-space: nowrap; color: var(--pk-text);
8
+ cursor: pointer; outline: none; transition: color .15s ease, background-color .15s ease, box-shadow .15s ease;
9
+ }
10
+ .pk-toggle:hover { background: var(--pk-accent); color: var(--pk-muted); }
11
+ .pk-toggle:disabled { pointer-events: none; opacity: .5; }
12
+ .pk-toggle[data-state="on"] { background: var(--pk-surface-2); color: var(--pk-text); }
13
+ .pk-toggle.outline { border-color: var(--pk-border); }
14
+ .pk-toggle.sm { height: 2rem; min-width: 2rem; padding-inline: .375rem; }
15
+ .pk-toggle.lg { height: 2.5rem; min-width: 2.5rem; padding-inline: .625rem; }
16
+ .pk-toggle svg { pointer-events: none; flex-shrink: 0; width: 1rem; height: 1rem; }
@@ -0,0 +1,59 @@
1
+ module PhlexKit
2
+ # Two-state pressable button. Ported from ruby_ui's RubyUI::Toggle. Renders a
3
+ # wrapper (display:contents) holding the <button> and an optional hidden input so
4
+ # the pressed state can post with a form. phlex-kit--toggle drives the state.
5
+ class Toggle < BaseComponent
6
+ VARIANTS = { default: nil, outline: "outline" }.freeze
7
+ SIZES = { sm: "sm", default: nil, lg: "lg" }.freeze
8
+
9
+ def self.modifier_classes(variant:, size:)
10
+ [VARIANTS.fetch(variant, nil), SIZES.fetch(size, nil)].compact
11
+ end
12
+
13
+ def initialize(pressed: false, name: nil, value: "1", unpressed_value: nil,
14
+ variant: :default, size: :default, disabled: false, wrapper: {}, **attrs)
15
+ @pressed = pressed
16
+ @name = name
17
+ @value = value
18
+ @unpressed_value = unpressed_value
19
+ @variant = variant.to_sym
20
+ @size = size.to_sym
21
+ @disabled = disabled
22
+ @wrapper = wrapper
23
+ @attrs = attrs
24
+ end
25
+
26
+ def view_template(&block)
27
+ span(**mix(wrapper_default_attrs, @wrapper)) do
28
+ button(**mix(button_default_attrs, @attrs), &block)
29
+ render_hidden_input if @name
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def classes
36
+ (["pk-toggle"] + self.class.modifier_classes(variant: @variant, size: @size)).join(" ")
37
+ end
38
+
39
+ def button_default_attrs
40
+ a = { type: "button", class: classes, aria: { pressed: @pressed.to_s },
41
+ data: { state: @pressed ? "on" : "off", phlex_kit__toggle_target: "button" } }
42
+ a[:disabled] = true if @disabled
43
+ a
44
+ end
45
+
46
+ def wrapper_default_attrs
47
+ { class: "pk-contents", data: { controller: "phlex-kit--toggle",
48
+ action: "click->phlex-kit--toggle#toggle",
49
+ phlex_kit__toggle_pressed_value: @pressed.to_s,
50
+ phlex_kit__toggle_value_value: @value.to_s,
51
+ phlex_kit__toggle_unpressed_value_value: @unpressed_value.to_s } }
52
+ end
53
+
54
+ def render_hidden_input
55
+ input(type: "hidden", name: @name, value: @pressed ? @value : @unpressed_value.to_s,
56
+ data: { phlex_kit__toggle_target: "input" })
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,10 @@
1
+ /* Co-located with toggle_group.rb. Theme tokens from the global stylesheet. */
2
+ .pk-toggle-group { display: inline-flex; width: fit-content; align-items: center; border-radius: calc(var(--pk-radius) - 2px); }
3
+ .pk-toggle-group.vertical { flex-direction: column; align-items: stretch; }
4
+ .pk-toggle-group.spaced { gap: .25rem; }
5
+ /* Joined (default, unspaced): square the inner corners. */
6
+ .pk-toggle-group:not(.spaced) .pk-toggle-group-item { border-radius: 0; }
7
+ .pk-toggle-group:not(.spaced):not(.vertical) .pk-toggle-group-item:first-of-type { border-top-left-radius: .375rem; border-bottom-left-radius: .375rem; }
8
+ .pk-toggle-group:not(.spaced):not(.vertical) .pk-toggle-group-item:last-of-type { border-top-right-radius: .375rem; border-bottom-right-radius: .375rem; }
9
+ .pk-toggle-group.vertical:not(.spaced) .pk-toggle-group-item:first-of-type { border-top-left-radius: .375rem; border-top-right-radius: .375rem; }
10
+ .pk-toggle-group.vertical:not(.spaced) .pk-toggle-group-item:last-of-type { border-bottom-left-radius: .375rem; border-bottom-right-radius: .375rem; }
@@ -0,0 +1,65 @@
1
+ module PhlexKit
2
+ # A set of related toggles (single- or multi-select). Ported from ruby_ui's
3
+ # RubyUI::ToggleGroup. Yields itself so items are declared with the group's
4
+ # shared context: ToggleGroup(type:) { |g| g.ToggleGroupItem(value:) { … } }.
5
+ class ToggleGroup < BaseComponent
6
+ VALID_TYPES = %i[single multiple].freeze
7
+
8
+ def initialize(type: :single, name: nil, value: nil, variant: :default, size: :default,
9
+ disabled: false, spacing: 0, orientation: :horizontal, **attrs)
10
+ @type = type.to_sym
11
+ raise ArgumentError, "type must be :single or :multiple" unless VALID_TYPES.include?(@type)
12
+ @name = name
13
+ @value = value
14
+ @variant = variant.to_sym
15
+ @size = size.to_sym
16
+ @disabled = disabled
17
+ @spacing = spacing.to_i
18
+ @orientation = orientation.to_sym
19
+ @attrs = attrs
20
+ end
21
+
22
+ def view_template(&block)
23
+ div(**mix(group_default_attrs, @attrs)) do
24
+ yield(self)
25
+ render_hidden_inputs
26
+ end
27
+ end
28
+
29
+ def item_context
30
+ { type: @type, variant: @variant, size: @size, disabled: @disabled,
31
+ selected_values: selected_values, spacing: @spacing, orientation: @orientation }
32
+ end
33
+
34
+ def ToggleGroupItem(**kwargs, &block)
35
+ render PhlexKit::ToggleGroupItem.new(group_context: item_context, **kwargs), &block
36
+ end
37
+
38
+ private
39
+
40
+ def selected_values
41
+ case @type
42
+ when :single then @value.nil? ? [] : [@value.to_s]
43
+ when :multiple then Array(@value).map(&:to_s)
44
+ end
45
+ end
46
+
47
+ def render_hidden_inputs
48
+ return unless @name
49
+ if @type == :single
50
+ input(type: "hidden", name: @name, value: selected_values.first.to_s, data: { phlex_kit__toggle_group_target: "input" })
51
+ else
52
+ selected_values.each { |v| input(type: "hidden", name: "#{@name}[]", value: v, data: { phlex_kit__toggle_group_target: "input" }) }
53
+ end
54
+ end
55
+
56
+ def group_default_attrs
57
+ { role: (@type == :single) ? "radiogroup" : "group",
58
+ class: ["pk-toggle-group", (@orientation == :vertical ? "vertical" : nil), (@spacing > 0 ? "spaced" : nil)].compact.join(" "),
59
+ data: { controller: "phlex-kit--toggle-group",
60
+ phlex_kit__toggle_group_type_value: @type.to_s,
61
+ phlex_kit__toggle_group_name_value: @name.to_s,
62
+ orientation: @orientation.to_s, spacing: @spacing.to_s } }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ module PhlexKit
2
+ class ToggleGroupItem < Toggle
3
+ def initialize(value:, group_context:, variant: nil, size: nil, **attrs)
4
+ @item_value = value.to_s
5
+ @group_context = group_context
6
+ pressed = group_context[:selected_values].include?(@item_value)
7
+ super(pressed: pressed, name: nil, value: @item_value,
8
+ variant: variant || group_context[:variant],
9
+ size: size || group_context[:size],
10
+ disabled: group_context[:disabled], **attrs)
11
+ end
12
+
13
+ def view_template(&block)
14
+ button(**mix(item_default_attrs, @attrs), &block)
15
+ end
16
+
17
+ private
18
+
19
+ def item_default_attrs
20
+ a = { type: "button",
21
+ class: (["pk-toggle"] + self.class.modifier_classes(variant: @variant, size: @size) + ["pk-toggle-group-item"]).join(" "),
22
+ data: { state: @pressed ? "on" : "off", value: @item_value,
23
+ phlex_kit__toggle_group_target: "item",
24
+ action: "click->phlex-kit--toggle-group#select keydown->phlex-kit--toggle-group#navigate" } }
25
+ a[:disabled] = true if @disabled
26
+ if @group_context[:type] == :single
27
+ a[:role] = "radio"
28
+ a[:aria] = { checked: @pressed.to_s }
29
+ a[:tabindex] = @pressed ? "0" : "-1"
30
+ else
31
+ a[:aria] = { pressed: @pressed.to_s }
32
+ a[:tabindex] = "0"
33
+ end
34
+ a
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ /* Co-located with tooltip.rb — UI::Tooltip (+ Trigger / Content). CSS-only:
2
+ the bubble is absolutely positioned above the trigger and revealed on the
3
+ wrapper's :hover / :focus-within (no JS, no @floating-ui). Theme tokens come
4
+ from the global stylesheet. */
5
+ .pk-tooltip { position: relative; display: inline-flex; }
6
+ .pk-tooltip-trigger { display: inline-flex; }
7
+ .pk-tooltip-content {
8
+ position: absolute;
9
+ bottom: calc(100% + 6px);
10
+ left: 50%;
11
+ transform: translateX(-50%);
12
+ z-index: 50;
13
+ width: max-content;
14
+ max-width: min(16rem, calc(100vw - 2rem));
15
+ padding: .35rem .6rem;
16
+ border-radius: calc(var(--pk-radius) - 2px);
17
+ border: 1px solid var(--pk-border);
18
+ background: var(--pk-surface-2);
19
+ color: var(--pk-text);
20
+ font-size: .8rem;
21
+ box-shadow: 0 4px 16px rgba(0, 0, 0, .4);
22
+ opacity: 0;
23
+ visibility: hidden;
24
+ pointer-events: none;
25
+ transition: opacity .12s ease;
26
+ }
27
+ .pk-tooltip:hover .pk-tooltip-content,
28
+ .pk-tooltip:focus-within .pk-tooltip-content { opacity: 1; visibility: visible; }
@@ -0,0 +1,15 @@
1
+ module PhlexKit
2
+ # Hover/focus tooltip. ruby_ui's version uses @floating-ui + a cloned template;
3
+ # we keep the Trigger/Content structure but reveal + position the bubble with
4
+ # pure CSS (no JS, no npm dep) — same call as the Select floating-ui strip.
5
+ # Bubble sits above the trigger. Compose TooltipTrigger + TooltipContent.
6
+ class Tooltip < BaseComponent
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ div(**mix({ class: "pk-tooltip" }, @attrs), &block)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module PhlexKit
2
+ # The tooltip bubble, revealed on hover/focus of its PhlexKit::Tooltip. See tooltip.rb.
3
+ class TooltipContent < BaseComponent
4
+ def initialize(**attrs)
5
+ @attrs = attrs
6
+ end
7
+
8
+ def view_template(&block)
9
+ div(**mix({ class: "pk-tooltip-content", role: "tooltip" }, @attrs), &block)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module PhlexKit
2
+ # The element a PhlexKit::Tooltip is anchored to. `tabindex: 0` so keyboard users can
3
+ # focus it (the bubble shows on `:focus-within`). See tooltip.rb.
4
+ class TooltipTrigger < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&block)
10
+ span(**mix({ class: "pk-tooltip-trigger", tabindex: 0 }, @attrs), &block)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module PhlexKit
2
+ # Blockquote, ported from ruby_ui's RubyUI::TypographyBlockquote. See heading.rb.
3
+ class Blockquote < BaseComponent
4
+ def initialize(**attrs)
5
+ @attrs = attrs
6
+ end
7
+
8
+ def view_template(&block)
9
+ blockquote(**mix({ class: "pk-blockquote" }, @attrs), &block)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ module PhlexKit
2
+ # Heading, ported from ruby_ui's RubyUI::Heading. `level:` (1–4) picks the
3
+ # element AND a default size; `as:` overrides the element; `size:` (1–9)
4
+ # overrides the size on ruby_ui's scale (text-xs … text-5xl). Presentational;
5
+ # attrs pass through via mix. Tailwind → vanilla `.pk-heading*` (typography.css).
6
+ class Heading < BaseComponent
7
+ SIZES = %w[1 2 3 4 5 6 7 8 9].freeze
8
+ # ruby_ui's level → default size mapping (h1 is big, h4 smaller).
9
+ LEVEL_SIZE = { "1" => "7", "2" => "6", "3" => "5", "4" => "4" }.freeze
10
+
11
+ def initialize(level: nil, as: nil, size: nil, **attrs)
12
+ @level = level
13
+ @as = as
14
+ @size = size
15
+ @attrs = attrs
16
+ end
17
+
18
+ def view_template(&block)
19
+ send(element, **mix({ class: classes }, @attrs), &block)
20
+ end
21
+
22
+ private
23
+
24
+ def element
25
+ (@as || (@level ? "h#{@level}" : "h1")).to_sym
26
+ end
27
+
28
+ def size_token
29
+ token = (@size || LEVEL_SIZE[@level.to_s] || "6").to_s
30
+ raise ArgumentError, "Invalid heading size: #{token.inspect}" unless SIZES.include?(token)
31
+ token
32
+ end
33
+
34
+ def classes
35
+ "pk-heading ui-heading-#{size_token}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ module PhlexKit
2
+ # Inline `<code>`, ported from ruby_ui's RubyUI::InlineCode. Replaces the legacy
3
+ # `code.key` utility. See heading.rb (same Typography family).
4
+ class InlineCode < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&block)
10
+ code(**mix({ class: "pk-inline-code" }, @attrs), &block)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module PhlexKit
2
+ # Inline text link, ported from ruby_ui's RubyUI::InlineLink — a brand-coloured
3
+ # underline-on-hover `<a>` for links inside prose (vs PhlexKit::Link, which is the
4
+ # button-styled link family). `href:` required; attrs pass through via mix.
5
+ class InlineLink < BaseComponent
6
+ def initialize(href:, **attrs)
7
+ @href = href
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ a(**mix({ href: @href, class: "pk-inline-link" }, @attrs), &block)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,48 @@
1
+ module PhlexKit
2
+ # Body text, ported from ruby_ui's RubyUI::Text. `as:` picks the element
3
+ # (default :p), `size:` (1–9 or xs/sm/base/lg/xl/2xl…) the size, `weight:` the
4
+ # font-weight — plus ruby_ui's `:muted` "weight", which is really the muted
5
+ # COLOR (a drop-in for the legacy `.muted` utility). Attrs pass through via mix.
6
+ # `SIZES`/`WEIGHTS` fetch fails loud. Tailwind → vanilla `.pk-text*`.
7
+ class Text < BaseComponent
8
+ SIZES = {
9
+ "1" => "1", "xs" => "1",
10
+ "2" => "2", "sm" => "2",
11
+ "3" => "3", "base" => "3",
12
+ "4" => "4", "lg" => "4",
13
+ "5" => "5", "xl" => "5",
14
+ "6" => "6", "2xl" => "6",
15
+ "7" => "7", "3xl" => "7",
16
+ "8" => "8", "4xl" => "8",
17
+ "9" => "9", "5xl" => "9"
18
+ }.freeze
19
+
20
+ # weight => modifier class (nil = regular). `:muted` is a colour, not a weight,
21
+ # matching ruby_ui's API.
22
+ WEIGHTS = {
23
+ regular: nil,
24
+ light: "pk-text-light",
25
+ medium: "pk-text-medium",
26
+ semibold: "pk-text-semibold",
27
+ bold: "pk-text-bold",
28
+ muted: "pk-text-muted"
29
+ }.freeze
30
+
31
+ def initialize(as: :p, size: "3", weight: :regular, **attrs)
32
+ @as = as
33
+ @size = size.to_s
34
+ @weight = weight.to_sym
35
+ @attrs = attrs
36
+ end
37
+
38
+ def view_template(&block)
39
+ send(@as, **mix({ class: classes }, @attrs), &block)
40
+ end
41
+
42
+ private
43
+
44
+ def classes
45
+ [ "pk-text", "pk-text-#{SIZES.fetch(@size)}", WEIGHTS.fetch(@weight) ].compact.join(" ")
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,50 @@
1
+ /* Co-located with the Typography family (heading.rb / text.rb / inline_code.rb /
2
+ inline_link.rb / blockquote.rb). ruby_ui's Tailwind → vanilla CSS on the
3
+ palette tokens. The size scale mirrors ruby_ui's text-xs … text-5xl. Theme
4
+ tokens come from the global stylesheet. */
5
+
6
+ /* Heading */
7
+ .pk-heading { margin: 0 0 .6rem; font-weight: 700; line-height: 1.2; letter-spacing: -.01em; }
8
+ .pk-heading-1 { font-size: .75rem; }
9
+ .pk-heading-2 { font-size: .875rem; }
10
+ .pk-heading-3 { font-size: 1rem; }
11
+ .pk-heading-4 { font-size: 1.125rem; }
12
+ .pk-heading-5 { font-size: 1.25rem; }
13
+ .pk-heading-6 { font-size: 1.5rem; }
14
+ .pk-heading-7 { font-size: 1.875rem; }
15
+ .pk-heading-8 { font-size: 2.25rem; }
16
+ .pk-heading-9 { font-size: 3rem; }
17
+
18
+ /* Text */
19
+ .pk-text { margin: 0; }
20
+ .pk-text-1 { font-size: .75rem; }
21
+ .pk-text-2 { font-size: .875rem; }
22
+ .pk-text-3 { font-size: 1rem; }
23
+ .pk-text-4 { font-size: 1.125rem; }
24
+ .pk-text-5 { font-size: 1.25rem; }
25
+ .pk-text-6 { font-size: 1.5rem; }
26
+ .pk-text-7 { font-size: 1.875rem; }
27
+ .pk-text-8 { font-size: 2.25rem; }
28
+ .pk-text-9 { font-size: 3rem; }
29
+ .pk-text-light { font-weight: 300; }
30
+ .pk-text-medium { font-weight: 500; }
31
+ .pk-text-semibold { font-weight: 600; }
32
+ .pk-text-bold { font-weight: 700; }
33
+ .pk-text-muted { color: var(--pk-muted); }
34
+
35
+ /* Inline code */
36
+ .pk-inline-code {
37
+ font-family: ui-monospace, monospace;
38
+ font-size: .875rem;
39
+ background: var(--pk-bg);
40
+ border: 1px solid var(--pk-border);
41
+ border-radius: 6px;
42
+ padding: .15rem .4rem;
43
+ }
44
+
45
+ /* Inline link */
46
+ .pk-inline-link { color: var(--pk-brand); font-weight: 500; text-underline-offset: 4px; }
47
+ .pk-inline-link:hover { text-decoration: underline; }
48
+
49
+ /* Blockquote */
50
+ .pk-blockquote { margin: 1rem 0; border-left: 2px solid var(--pk-border); padding-left: 1rem; font-style: italic; }
@@ -0,0 +1,59 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="phlex-kit--accordion"
4
+ // Ported from ruby_ui's ruby-ui--accordion controller, reworked to use the native
5
+ // Web Animations API instead of the `motion` package (no extra JS dependency).
6
+ export default class extends Controller {
7
+ static targets = ["icon", "content"]
8
+ static values = {
9
+ open: { type: Boolean, default: false },
10
+ animationDuration: { type: Number, default: 150 },
11
+ rotateIcon: { type: Number, default: 180 },
12
+ }
13
+
14
+ connect() {
15
+ const d = this.animationDurationValue
16
+ this.animationDurationValue = 0
17
+ this.openValue ? this.open() : this.close()
18
+ this.animationDurationValue = d
19
+ }
20
+
21
+ toggle() { this.openValue = !this.openValue }
22
+ openValueChanged(isOpen) { isOpen ? this.open() : this.close() }
23
+
24
+ open() {
25
+ if (!this.hasContentTarget) return
26
+ this.reveal()
27
+ if (this.hasIconTarget) this.rotate()
28
+ }
29
+
30
+ close() {
31
+ if (!this.hasContentTarget) return
32
+ this.hide()
33
+ if (this.hasIconTarget) this.rotate()
34
+ }
35
+
36
+ reveal() {
37
+ const c = this.contentTarget
38
+ c.removeAttribute("hidden")
39
+ c.dataset.state = "open"
40
+ const h = c.scrollHeight
41
+ c.animate([{ height: c.style.height || "0px" }, { height: `${h}px` }],
42
+ { duration: this.animationDurationValue, easing: "ease-in-out" })
43
+ c.style.height = `${h}px`
44
+ }
45
+
46
+ hide() {
47
+ const c = this.contentTarget
48
+ c.dataset.state = "closed"
49
+ const from = c.scrollHeight
50
+ const a = c.animate([{ height: `${from}px` }, { height: "0px" }],
51
+ { duration: this.animationDurationValue, easing: "ease-in-out" })
52
+ c.style.height = "0px"
53
+ a.finished.then(() => { if (c.dataset.state === "closed") c.setAttribute("hidden", "") }).catch(() => {})
54
+ }
55
+
56
+ rotate() {
57
+ this.iconTarget.style.transform = `rotate(${this.openValue ? this.rotateIconValue : 0}deg)`
58
+ }
59
+ }
@@ -0,0 +1,24 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ // Ported from ruby_ui's phlex-kit--alert-dialog controller. open() clones the
4
+ // <template> content into <body> as a modal (the clone carries its own instance
5
+ // of this controller); dismiss() removes it. The only change from upstream: lock
6
+ // body scroll via inline style rather than a Tailwind `overflow-hidden` class.
7
+ export default class extends Controller {
8
+ static targets = ["content"];
9
+ static values = { open: { type: Boolean, default: false } };
10
+
11
+ connect() {
12
+ if (this.openValue) this.open();
13
+ }
14
+
15
+ open() {
16
+ document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
17
+ document.body.style.overflow = "hidden";
18
+ }
19
+
20
+ dismiss() {
21
+ document.body.style.overflow = "";
22
+ this.element.remove();
23
+ }
24
+ }