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,82 @@
1
+ module PhlexKit
2
+ # Sortable <th>: a link cycling asc → desc → unsorted, preserving the rest of
3
+ # the query string (page resets). See data_table.rb.
4
+ class DataTableSortHead < BaseComponent
5
+ def initialize(column_key:, label:, sort: nil, direction: nil, sort_param: "sort", direction_param: "direction", page_param: "page", path: "", query: {}, **attrs)
6
+ @column_key = column_key
7
+ @label = label
8
+ @sort = sort
9
+ @direction = direction
10
+ @sort_param = sort_param
11
+ @direction_param = direction_param
12
+ @page_param = page_param
13
+ @path = path
14
+ @query = query.to_h.transform_keys(&:to_s)
15
+ @attrs = attrs
16
+ end
17
+
18
+ def view_template
19
+ render TableHead.new(**mix({ class: "pk-data-table-sort-head" }, @attrs)) do
20
+ a(href: sort_href, class: "pk-data-table-sort-link") do
21
+ plain @label
22
+ sort_icon
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def current_direction
30
+ (@sort.to_s == @column_key.to_s) ? @direction : nil
31
+ end
32
+
33
+ def next_params
34
+ next_dir = { nil => "asc", "asc" => "desc", "desc" => nil }[current_direction]
35
+ base = @query.except(@sort_param, @direction_param, @page_param)
36
+ next_dir ? base.merge(@sort_param => @column_key.to_s, @direction_param => next_dir) : base
37
+ end
38
+
39
+ def sort_href
40
+ qs = build_query(next_params)
41
+ qs.empty? ? @path : "#{@path}?#{qs}"
42
+ end
43
+
44
+ def build_query(hash)
45
+ hash.flat_map { |k, v|
46
+ Array(v).map { |val| "#{CGI.escape(k.to_s)}=#{CGI.escape(val.to_s)}" }
47
+ }.join("&")
48
+ end
49
+
50
+ def sort_icon
51
+ icon_class = [ "pk-data-table-sort-icon", (current_direction ? nil : "unsorted") ].compact.join(" ")
52
+ case current_direction
53
+ when "asc"
54
+ sort_svg(icon_class) { |s| s.polyline(points: "18 15 12 9 6 15") }
55
+ when "desc"
56
+ sort_svg(icon_class) { |s| s.polyline(points: "6 9 12 15 18 9") }
57
+ else
58
+ sort_svg(icon_class) do |s|
59
+ s.polyline(points: "8 15 12 19 16 15")
60
+ s.polyline(points: "8 9 12 5 16 9")
61
+ end
62
+ end
63
+ end
64
+
65
+ def sort_svg(icon_class, &)
66
+ svg(
67
+ xmlns: "http://www.w3.org/2000/svg",
68
+ width: "12",
69
+ height: "12",
70
+ viewbox: "0 0 24 24",
71
+ fill: "none",
72
+ stroke: "currentColor",
73
+ "stroke-width": "2",
74
+ "stroke-linecap": "round",
75
+ "stroke-linejoin": "round",
76
+ class: icon_class,
77
+ "aria-hidden": "true",
78
+ &
79
+ )
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,13 @@
1
+ module PhlexKit
2
+ # Row above the table for search / bulk actions / column toggle.
3
+ # See data_table.rb.
4
+ class DataTableToolbar < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&)
10
+ div(**mix({ class: "pk-data-table-toolbar" }, @attrs), &)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ /* Co-located with date_picker.rb — UI::DatePicker (an Input + Popover +
2
+ Calendar composition; those parts bring their own styles). Geometry from
3
+ ruby_ui (260px column, grid field with gap, full-width trigger). Theme tokens
4
+ come from the global stylesheet. */
5
+ .pk-date-picker {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 1rem;
9
+ width: 260px;
10
+ }
11
+ .pk-date-picker-trigger { width: 100%; }
12
+ .pk-date-picker-field {
13
+ display: grid;
14
+ width: 100%;
15
+ max-width: 24rem;
16
+ align-items: center;
17
+ gap: .375rem;
18
+ }
19
+ .pk-date-picker-label {
20
+ font-size: .875rem;
21
+ font-weight: 500;
22
+ line-height: 1.1;
23
+ }
24
+ /* The panel hugs the calendar instead of stretching to the popover minimum. */
25
+ .pk-date-picker .pk-popover-content {
26
+ width: max-content;
27
+ padding: 0;
28
+ }
@@ -0,0 +1,71 @@
1
+ module PhlexKit
2
+ # Date picker, ported from ruby_ui's RubyUI::DatePicker: a labelled
3
+ # PhlexKit::Input inside a PhlexKit::Popover whose panel holds a
4
+ # PhlexKit::Calendar. The input carries the phlex-kit--calendar-input
5
+ # controller and the calendar's outlet points at its id, so picking a day
6
+ # writes the formatted date into the field. Upstream's popover_options
7
+ # (hover trigger via @floating-ui) are gone — the kit popover is click-only,
8
+ # CSS-positioned. Tailwind → vanilla `.pk-date-picker*` (date_picker.css).
9
+ class DatePicker < BaseComponent
10
+ def initialize(
11
+ id: nil,
12
+ name: nil,
13
+ label: "Select a date",
14
+ value: nil,
15
+ placeholder: "Select a date",
16
+ selected_date: value,
17
+ date_format: "yyyy-MM-dd",
18
+ input_attrs: {},
19
+ calendar_attrs: {},
20
+ trigger_attrs: {},
21
+ content_attrs: {},
22
+ **attrs
23
+ )
24
+ @id = id || "date-picker-#{SecureRandom.hex(4)}"
25
+ @name = name
26
+ @label = label
27
+ @value = value || selected_date&.to_s
28
+ @placeholder = placeholder
29
+ @selected_date = selected_date
30
+ @date_format = date_format
31
+ @input_attrs = input_attrs
32
+ @calendar_attrs = calendar_attrs
33
+ @trigger_attrs = trigger_attrs
34
+ @content_attrs = content_attrs
35
+ @attrs = attrs
36
+ end
37
+
38
+ def view_template
39
+ div(**mix({ class: "pk-date-picker" }, @attrs)) do
40
+ render Popover.new do
41
+ render PopoverTrigger.new(**trigger_attrs) do
42
+ div(class: "pk-date-picker-field") do
43
+ label(class: "pk-date-picker-label", for: @id) { @label } if @label
44
+ render Input.new(**input_attrs)
45
+ end
46
+ end
47
+ render PopoverContent.new(**@content_attrs) do
48
+ render Calendar.new(input_id: "##{@id}", selected_date: @selected_date, date_format: @date_format, **@calendar_attrs)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def trigger_attrs
57
+ mix({ class: "pk-date-picker-trigger" }, @trigger_attrs)
58
+ end
59
+
60
+ def input_attrs
61
+ mix({
62
+ type: :text,
63
+ placeholder: @placeholder,
64
+ id: @id,
65
+ name: @name,
66
+ value: @value,
67
+ data: { controller: "phlex-kit--calendar-input" }
68
+ }.compact, @input_attrs)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,32 @@
1
+ /* Co-located with dialog.rb. Native <dialog> centered by the UA; we style the box
2
+ + ::backdrop. Theme tokens from the global stylesheet. */
3
+ .pk-dialog-trigger { display: inline-block; }
4
+ .pk-dialog {
5
+ position: fixed; z-index: 50; width: 100%; max-width: 32rem; max-height: 100vh;
6
+ overflow-y: auto; gap: 1rem; border: 1px solid var(--pk-border);
7
+ border-radius: var(--pk-radius); background: var(--pk-surface); color: var(--pk-text);
8
+ padding: 1.5rem; box-shadow: 0 10px 30px rgb(0 0 0 / .35);
9
+ }
10
+ .pk-dialog[open] { display: flex; flex-direction: column; }
11
+ .pk-dialog::backdrop { background: rgb(0 0 0 / .5); backdrop-filter: blur(2px); }
12
+ .pk-dialog.xs { max-width: 24rem; }
13
+ .pk-dialog.sm { max-width: 28rem; }
14
+ .pk-dialog.lg { max-width: 42rem; }
15
+ .pk-dialog.xl { max-width: 56rem; }
16
+ .pk-dialog.full { max-width: 100%; }
17
+ .pk-dialog-header { display: flex; flex-direction: column; gap: .375rem; text-align: center; }
18
+ .pk-dialog-footer { display: flex; flex-direction: column-reverse; gap: .5rem; }
19
+ .pk-dialog-title { font-size: 1.125rem; font-weight: 600; line-height: 1; letter-spacing: -.01em; }
20
+ .pk-dialog-description { font-size: .875rem; color: var(--pk-muted); }
21
+ .pk-dialog-middle { padding-block: 1rem; }
22
+ .pk-overlay-close {
23
+ position: absolute; inset-inline-end: 1rem; top: 1rem; border: 0; background: none;
24
+ cursor: pointer; border-radius: calc(var(--pk-radius) - 4px); opacity: .7; color: var(--pk-text);
25
+ transition: opacity .15s ease;
26
+ }
27
+ .pk-overlay-close:hover { opacity: 1; }
28
+ @media (min-width: 640px) {
29
+ .pk-dialog-header { text-align: left; }
30
+ .pk-dialog-footer { flex-direction: row; justify-content: flex-end; }
31
+ .pk-dialog-footer > * + * { margin-inline-start: .5rem; }
32
+ }
@@ -0,0 +1,14 @@
1
+ module PhlexKit
2
+ # Modal dialog built on the native <dialog> element (showModal). Ported from
3
+ # ruby_ui's RubyUI::Dialog. Compose Dialog > (DialogTrigger + DialogContent >
4
+ # DialogHeader/Title/Description/Middle/Footer). phlex-kit--dialog.
5
+ class Dialog < BaseComponent
6
+ def initialize(open: false, **attrs)
7
+ @open = open
8
+ @attrs = attrs
9
+ end
10
+ def view_template(&)
11
+ div(**mix({ data: { controller: "phlex-kit--dialog", phlex_kit__dialog_open_value: @open } }, @attrs), &)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module PhlexKit
2
+ class DialogContent < BaseComponent
3
+ SIZES = { xs: "xs", sm: "sm", md: nil, lg: "lg", xl: "xl", full: "full" }.freeze
4
+ def initialize(size: :md, **attrs)
5
+ @size = size.to_sym
6
+ @attrs = attrs
7
+ end
8
+ def view_template(&block)
9
+ cls = ["pk-dialog", SIZES.fetch(@size)].compact.join(" ")
10
+ dialog(**mix({ class: cls, data: { phlex_kit__dialog_target: "dialog", action: "click->phlex-kit--dialog#backdropClick" } }, @attrs)) do
11
+ yield
12
+ button(type: "button", class: "pk-overlay-close", data: { action: "click->phlex-kit--dialog#dismiss" }) do
13
+ svg(width: "15", height: "15", viewbox: "0 0 15 15", fill: "none", xmlns: "http://www.w3.org/2000/svg") do |s|
14
+ s.path(fill_rule: "evenodd", clip_rule: "evenodd", fill: "currentColor", d: "M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z")
15
+ end
16
+ span(class: "pk-sr-only") { "Close" }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class DialogDescription < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = p(**mix({ class: "pk-dialog-description" }, @attrs), &)
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class DialogFooter < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = div(**mix({ class: "pk-dialog-footer" }, @attrs), &)
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class DialogHeader < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = div(**mix({ class: "pk-dialog-header" }, @attrs), &)
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class DialogMiddle < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = div(**mix({ class: "pk-dialog-middle" }, @attrs), &)
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class DialogTitle < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = h3(**mix({ class: "pk-dialog-title" }, @attrs), &)
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module PhlexKit
2
+ class DialogTrigger < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&)
5
+ div(**mix({ class: "pk-dialog-trigger", data: { action: "click->phlex-kit--dialog#open" } }, @attrs), &)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,54 @@
1
+ /* Co-located with drawer.rb — UI::Drawer, shadcn parity (vaul replaced by the
2
+ kit's sheet clone machinery): dimmed overlay, bottom sheet with grab handle
3
+ sliding up. */
4
+ .pk-drawer-trigger, .pk-drawer-close { display: inline-block; }
5
+ .pk-drawer-overlay {
6
+ position: fixed;
7
+ inset: 0;
8
+ z-index: 50;
9
+ background: rgb(0 0 0 / .5);
10
+ }
11
+ .pk-drawer {
12
+ position: fixed;
13
+ inset-inline: 0;
14
+ bottom: 0;
15
+ z-index: 50;
16
+ display: flex;
17
+ flex-direction: column;
18
+ max-height: 85vh;
19
+ border: 1px solid var(--pk-border);
20
+ border-bottom: 0;
21
+ border-top-left-radius: calc(var(--pk-radius) + 4px);
22
+ border-top-right-radius: calc(var(--pk-radius) + 4px);
23
+ background: var(--pk-surface);
24
+ color: var(--pk-text);
25
+ animation: pk-drawer-up .3s ease;
26
+ }
27
+ @keyframes pk-drawer-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
28
+ .pk-drawer-handle {
29
+ flex: none;
30
+ width: 100px;
31
+ height: .5rem;
32
+ margin: .75rem auto 0;
33
+ border-radius: 9999px;
34
+ background: var(--pk-surface-2);
35
+ }
36
+ .pk-drawer-header {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: .375rem;
40
+ padding: 1rem;
41
+ text-align: center;
42
+ }
43
+ .pk-drawer-title { margin: 0; font-size: 1.125rem; font-weight: 600; }
44
+ .pk-drawer-description { margin: 0; font-size: .875rem; color: var(--pk-muted); }
45
+ .pk-drawer-footer {
46
+ display: flex;
47
+ flex-direction: column;
48
+ gap: .5rem;
49
+ margin-top: auto;
50
+ padding: 1rem;
51
+ }
52
+ @media (min-width: 640px) {
53
+ .pk-drawer-header { text-align: left; }
54
+ }
@@ -0,0 +1,18 @@
1
+ module PhlexKit
2
+ # Bottom drawer, ported from shadcn/ui's Drawer. The vaul dependency is
3
+ # replaced by reusing the kit's sheet machinery — the phlex-kit--sheet
4
+ # controller clones DrawerContent's <template> into <body> — with drawer
5
+ # chrome (bottom sheet, grab handle, slide-up animation). Compose Drawer >
6
+ # DrawerTrigger + DrawerContent(DrawerHeader(DrawerTitle + DrawerDescription)
7
+ # + body + DrawerFooter [+ DrawerClose]). `.pk-drawer*` (drawer.css).
8
+ class Drawer < BaseComponent
9
+ def initialize(open: false, **attrs)
10
+ @open = open
11
+ @attrs = attrs
12
+ end
13
+
14
+ def view_template(&)
15
+ div(**mix({ data: { controller: "phlex-kit--sheet", phlex_kit__sheet_open_value: @open } }, @attrs), &)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module PhlexKit
2
+ # Closes the drawer from inside the cloned panel (wrap a Button). See drawer.rb.
3
+ class DrawerClose < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&)
6
+ div(**mix({ class: "pk-drawer-close", data: { action: "click->phlex-kit--sheet-content#close" } }, @attrs), &)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module PhlexKit
2
+ # <template> holding the drawer overlay + bottom panel (cloned into <body>
3
+ # on open, like SheetContent). See drawer.rb.
4
+ class DrawerContent < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&block)
10
+ template(data: { phlex_kit__sheet_target: "content" }) do
11
+ div(data: { controller: "phlex-kit--sheet-content" }) do
12
+ div(class: "pk-drawer-overlay", data: { action: "click->phlex-kit--sheet-content#close" })
13
+ div(**mix({ class: "pk-drawer", role: "dialog", aria: { modal: "true" } }, @attrs)) do
14
+ div(class: "pk-drawer-handle", aria: { hidden: "true" })
15
+ yield if block
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module PhlexKit
2
+ # See drawer.rb.
3
+ class DrawerDescription < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&) = p(**mix({ class: "pk-drawer-description" }, @attrs), &)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module PhlexKit
2
+ # See drawer.rb.
3
+ class DrawerFooter < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&) = div(**mix({ class: "pk-drawer-footer" }, @attrs), &)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module PhlexKit
2
+ # See drawer.rb.
3
+ class DrawerHeader < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&) = div(**mix({ class: "pk-drawer-header" }, @attrs), &)
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module PhlexKit
2
+ # See drawer.rb.
3
+ class DrawerTitle < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&) = h2(**mix({ class: "pk-drawer-title" }, @attrs), &)
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module PhlexKit
2
+ # Opens the Drawer (wrap a Button). See drawer.rb.
3
+ class DrawerTrigger < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&)
6
+ div(**mix({ class: "pk-drawer-trigger", data: { action: "click->phlex-kit--sheet#open" } }, @attrs), &)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ /* Co-located with dropdown_menu.rb — UI::DropdownMenu (+ Trigger / Content / Item
2
+ / Label / Separator). ruby_ui's structure + Stimulus kept; @floating-ui dropped
3
+ in favour of CSS positioning (panel below the trigger, or above with
4
+ `side: :top`). Tailwind → vanilla. Theme tokens come from the global stylesheet. */
5
+ .pk-dropdown-menu { position: relative; display: inline-block; }
6
+ .pk-dropdown-menu-trigger { display: inline-block; }
7
+
8
+ .pk-dropdown-menu-content { position: absolute; top: 100%; left: 0; z-index: 50; margin-top: 4px; }
9
+ .pk-dropdown-menu-content-up { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 4px; }
10
+ .pk-dropdown-menu-content.hidden { display: none; }
11
+ .pk-dropdown-menu-viewport {
12
+ min-width: 10rem;
13
+ border-radius: calc(var(--pk-radius) - 2px);
14
+ border: 1px solid var(--pk-border);
15
+ background: var(--pk-surface);
16
+ color: var(--pk-text);
17
+ padding: .25rem;
18
+ box-shadow: 0 4px 16px rgba(0, 0, 0, .4);
19
+ }
20
+
21
+ .pk-dropdown-menu-item {
22
+ display: flex;
23
+ align-items: center;
24
+ width: 100%;
25
+ cursor: pointer;
26
+ user-select: none;
27
+ border-radius: calc(var(--pk-radius) - 4px);
28
+ padding: .375rem .5rem;
29
+ font-size: .875rem;
30
+ color: var(--pk-text);
31
+ outline: none;
32
+ }
33
+ .pk-dropdown-menu-item:hover,
34
+ .pk-dropdown-menu-item:focus,
35
+ .pk-dropdown-menu-item[aria-selected="true"] { background: var(--pk-accent); text-decoration: none; }
36
+
37
+ .pk-dropdown-menu-label { margin: 0; padding: .375rem .5rem; font-size: .875rem; font-weight: 600; }
38
+ .pk-dropdown-menu-separator { height: 1px; margin: .25rem -.25rem; background: var(--pk-border); }
@@ -0,0 +1,22 @@
1
+ module PhlexKit
2
+ # Dropdown menu, ported from ruby_ui's RubyUI::DropdownMenu. Keeps the Stimulus
3
+ # controller (toggle, click-outside, keyboard nav) but — like PhlexKit::Select — drops
4
+ # the @floating-ui/dom dependency and positions the panel with CSS. Compose
5
+ # Trigger + Content (+ Item / Label / Separator). Tailwind → vanilla
6
+ # `.pk-dropdown-menu*` (dropdown_menu.css).
7
+ class DropdownMenu < BaseComponent
8
+ def initialize(**attrs)
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template(&block)
13
+ div(**mix({
14
+ class: "pk-dropdown-menu",
15
+ data: {
16
+ controller: "phlex-kit--dropdown-menu",
17
+ action: "click@window->phlex-kit--dropdown-menu#onClickOutside"
18
+ }
19
+ }, @attrs), &block)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module PhlexKit
2
+ # The dropdown panel. Hidden until the trigger toggles it. `side:` is :bottom
3
+ # (default, opens below) or :top (opens above — for triggers near the bottom of
4
+ # the viewport, e.g. the sidebar footer). See dropdown_menu.rb.
5
+ class DropdownMenuContent < BaseComponent
6
+ def initialize(side: :bottom, **attrs)
7
+ @side = side.to_sym
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ div(class: wrapper_classes, data: { phlex_kit__dropdown_menu_target: "content" }) do
13
+ div(**mix({ class: "pk-dropdown-menu-viewport" }, @attrs), &block)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def wrapper_classes
20
+ [ "pk-dropdown-menu-content", ("pk-dropdown-menu-content-up" if @side == :top), "hidden" ].compact.join(" ")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module PhlexKit
2
+ # A menu row in a PhlexKit::DropdownMenuContent. `as: :a` (default, pass `href:`) or
3
+ # `:div` (e.g. to wrap a button_to). Closes the menu on click. See dropdown_menu.rb.
4
+ class DropdownMenuItem < BaseComponent
5
+ def initialize(as: :a, href: "#", **attrs)
6
+ @as = as.to_sym
7
+ @href = href
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ base = {
13
+ role: "menuitem",
14
+ tabindex: "-1",
15
+ class: "pk-dropdown-menu-item",
16
+ data: { phlex_kit__dropdown_menu_target: "menuItem", action: "click->phlex-kit--dropdown-menu#close" }
17
+ }
18
+ base[:href] = @href unless @as == :div
19
+ send(@as, **mix(base, @attrs), &block)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module PhlexKit
2
+ # A non-interactive heading inside a PhlexKit::DropdownMenuContent. See dropdown_menu.rb.
3
+ class DropdownMenuLabel < BaseComponent
4
+ def initialize(**attrs)
5
+ @attrs = attrs
6
+ end
7
+
8
+ def view_template(&block)
9
+ h3(**mix({ class: "pk-dropdown-menu-label" }, @attrs), &block)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module PhlexKit
2
+ # A horizontal divider between groups of PhlexKit::DropdownMenuItems. See dropdown_menu.rb.
3
+ class DropdownMenuSeparator < BaseComponent
4
+ def initialize(**attrs)
5
+ @attrs = attrs
6
+ end
7
+
8
+ def view_template
9
+ div(**mix({ role: "separator", "aria-orientation": "horizontal", class: "pk-dropdown-menu-separator" }, @attrs))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module PhlexKit
2
+ # The control that opens a PhlexKit::DropdownMenu (wrap a PhlexKit::Button). See dropdown_menu.rb.
3
+ class DropdownMenuTrigger < BaseComponent
4
+ def initialize(**attrs)
5
+ @attrs = attrs
6
+ end
7
+
8
+ def view_template(&block)
9
+ div(**mix({
10
+ class: "pk-dropdown-menu-trigger",
11
+ data: { phlex_kit__dropdown_menu_target: "trigger", action: "click->phlex-kit--dropdown-menu#toggle" }
12
+ }, @attrs), &block)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ /* Co-located with empty.rb. Theme tokens from the global stylesheet. */
2
+ .pk-empty {
3
+ display: flex; width: 100%; min-width: 0; flex: 1; flex-direction: column;
4
+ align-items: center; justify-content: center; gap: 1rem;
5
+ border: 1px dashed var(--pk-border); border-radius: 1.5rem;
6
+ padding: 3rem; text-align: center;
7
+ }
8
+ .pk-empty-header { display: flex; max-width: 24rem; flex-direction: column; align-items: center; gap: .5rem; }
9
+ .pk-empty-media {
10
+ margin-bottom: .5rem; display: flex; flex: none;
11
+ align-items: center; justify-content: center;
12
+ }
13
+ .pk-empty-media.icon {
14
+ height: 2.5rem; width: 2.5rem; border-radius: .75rem;
15
+ background: var(--pk-surface-2); color: var(--pk-text);
16
+ }
17
+ .pk-empty-media.icon > svg:not([class*="size-"]) { width: 1.25rem; height: 1.25rem; }
18
+ .pk-empty-title { font-size: 1.125rem; font-weight: 500; letter-spacing: -.01em; }
19
+ .pk-empty-description { font-size: .875rem; line-height: 1.625; color: var(--pk-muted); }
20
+ .pk-empty-description a { text-decoration: underline; text-underline-offset: 4px; }
21
+ .pk-empty-description a:hover { color: var(--pk-brand); }
22
+ .pk-empty-content {
23
+ display: flex; width: 100%; max-width: 24rem; min-width: 0;
24
+ flex-direction: column; align-items: center; gap: 1rem; font-size: .875rem;
25
+ }
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class Empty < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = div(**mix({ class: "pk-empty" }, @attrs), &)
5
+ end
6
+ end