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,27 @@
1
+ module PhlexKit
2
+ # See navigation_menu.rb.
3
+ class NavigationMenuTrigger < BaseComponent
4
+ def initialize(**attrs) = (@attrs = attrs)
5
+ def view_template(&block)
6
+ button(**mix({
7
+ type: :button,
8
+ class: "pk-navigation-menu-trigger",
9
+ aria: { haspopup: "menu", expanded: "false" },
10
+ data: { action: "click->phlex-kit--menubar#toggle mouseenter->phlex-kit--menubar#switch" }
11
+ }, @attrs)) do
12
+ block&.call
13
+ chevron
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def chevron
20
+ svg(xmlns: "http://www.w3.org/2000/svg", viewbox: "0 0 24 24", fill: "none",
21
+ stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round",
22
+ "stroke-linejoin": "round", class: "pk-navigation-menu-chevron", "aria-hidden": "true") do |s|
23
+ s.path(d: "m6 9 6 6 6-6")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ /* Co-located with pagination.rb. Items reuse .pk-button. Tokens global. */
2
+ .pk-pagination { margin-inline: auto; display: flex; width: 100%; justify-content: center; }
3
+ .pk-pagination-content { display: flex; flex-direction: row; align-items: center; gap: .25rem; list-style: none; margin: 0; padding: 0; }
4
+ .pk-pagination-ellipsis { display: flex; height: 2.25rem; width: 2.25rem; align-items: center; justify-content: center; }
5
+ .pk-pagination-ellipsis > svg { width: 1rem; height: 1rem; }
@@ -0,0 +1,10 @@
1
+ module PhlexKit
2
+ # Page navigation. Ported from ruby_ui's RubyUI::Pagination. Compose Pagination >
3
+ # PaginationContent > PaginationItem(href:, active:) / PaginationEllipsis.
4
+ class Pagination < BaseComponent
5
+ def initialize(**attrs) = (@attrs = attrs)
6
+ def view_template(&)
7
+ nav(**mix({ class: "pk-pagination", role: "navigation", aria: { label: "pagination" } }, @attrs), &)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ module PhlexKit
2
+ class PaginationContent < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&) = ul(**mix({ class: "pk-pagination-content" }, @attrs), &)
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module PhlexKit
2
+ class PaginationEllipsis < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template
5
+ li do
6
+ span(**mix({ class: "pk-pagination-ellipsis", aria: { hidden: true } }, @attrs)) do
7
+ svg(xmlns: "http://www.w3.org/2000/svg", viewbox: "0 0 24 24", fill: "none", stroke: "currentColor", stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round") do |s|
8
+ s.circle(cx: "12", cy: "12", r: "1")
9
+ s.circle(cx: "19", cy: "12", r: "1")
10
+ s.circle(cx: "5", cy: "12", r: "1")
11
+ end
12
+ span(class: "pk-sr-only") { "More pages" }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module PhlexKit
2
+ class PaginationItem < BaseComponent
3
+ def initialize(href: "#", active: false, **attrs)
4
+ @href = href
5
+ @active = active
6
+ @attrs = attrs
7
+ end
8
+ def view_template(&block)
9
+ li do
10
+ a(**mix({ href: @href, class: "pk-button #{@active ? "outline" : "ghost"}", aria: { current: (@active ? "page" : nil) } }, @attrs), &block)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ /* Co-located with popover.rb. CSS-positioned panel. Tokens from the global stylesheet. */
2
+ .pk-popover { position: relative; display: inline-block; }
3
+ .pk-popover-trigger { display: inline-block; }
4
+ .pk-popover-content {
5
+ position: absolute; z-index: 50; top: calc(100% + .5rem); left: 0;
6
+ min-width: 12rem; border: 1px solid var(--pk-border); border-radius: calc(var(--pk-radius) - 2px);
7
+ background: var(--pk-surface); color: var(--pk-text); padding: .75rem;
8
+ box-shadow: 0 8px 24px rgb(0 0 0 / .3); outline: none;
9
+ }
@@ -0,0 +1,11 @@
1
+ module PhlexKit
2
+ # Click-triggered floating panel, positioned with CSS (no @floating-ui). Ported
3
+ # from ruby_ui's RubyUI::Popover. Compose Popover > (PopoverTrigger +
4
+ # PopoverContent). phlex-kit--popover.
5
+ class Popover < BaseComponent
6
+ def initialize(**attrs) = (@attrs = attrs)
7
+ def view_template(&)
8
+ div(**mix({ class: "pk-popover", data: { controller: "phlex-kit--popover" } }, @attrs), &)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module PhlexKit
2
+ class PopoverContent < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&)
5
+ div(**mix({ class: "pk-popover-content pk-hidden", data: { phlex_kit__popover_target: "content", state: "closed" } }, @attrs), &)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module PhlexKit
2
+ class PopoverTrigger < BaseComponent
3
+ def initialize(**attrs) = (@attrs = attrs)
4
+ def view_template(&)
5
+ div(**mix({ class: "pk-popover-trigger", data: { phlex_kit__popover_target: "trigger", action: "click->phlex-kit--popover#toggle" } }, @attrs), &)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ /* Co-located with progress.rb — UI::Progress. ruby_ui's Tailwind (h-2 rounded-full
2
+ bg-primary/20, indicator bg-primary translated by value) as vanilla CSS on the
3
+ palette tokens. Theme tokens come from the global stylesheet. */
4
+ .pk-progress {
5
+ position: relative;
6
+ height: .5rem;
7
+ width: 100%;
8
+ overflow: hidden;
9
+ border-radius: 999px;
10
+ background: color-mix(in oklab, var(--pk-brand) 20%, transparent);
11
+ }
12
+ .pk-progress-indicator {
13
+ height: 100%;
14
+ width: 100%;
15
+ background: var(--pk-brand);
16
+ transition: transform .3s ease;
17
+ }
@@ -0,0 +1,25 @@
1
+ module PhlexKit
2
+ # Progress bar, ported from ruby_ui's RubyUI::Progress. `value:` (0–100,
3
+ # clamped) drives an inner indicator translated into view. Presentational, no
4
+ # JS; width comes from a caller `class:`/`style:`. Tailwind → vanilla
5
+ # `.pk-progress*` (progress.css).
6
+ class Progress < BaseComponent
7
+ def initialize(value: 0, **attrs)
8
+ @value = value.to_f.clamp(0.0, 100.0)
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template
13
+ div(**mix({
14
+ role: "progressbar",
15
+ "aria-valuenow": @value,
16
+ "aria-valuemin": 0,
17
+ "aria-valuemax": 100,
18
+ "aria-valuetext": "#{@value}%",
19
+ class: "pk-progress"
20
+ }, @attrs)) do
21
+ div(class: "pk-progress-indicator", style: "transform: translateX(-#{100 - @value}%)")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ /* Co-located with radio_button.rb. Theme tokens from the global stylesheet. */
2
+ .pk-radio {
3
+ height: 1rem;
4
+ width: 1rem;
5
+ flex: none;
6
+ border-radius: 9999px;
7
+ accent-color: var(--pk-brand);
8
+ }
9
+ .pk-radio:disabled { cursor: not-allowed; opacity: .5; }
@@ -0,0 +1,19 @@
1
+ module PhlexKit
2
+ # Single radio input. Ported from ruby_ui's RubyUI::RadioButton. Registers as
3
+ # a phlex-kit--form-field input target, so it live-validates inside a
4
+ # PhlexKit::FormField (Stimulus ignores the wiring otherwise).
5
+ class RadioButton < BaseComponent
6
+ def initialize(**attrs) = (@attrs = attrs)
7
+
8
+ def view_template
9
+ input(**mix({
10
+ type: "radio",
11
+ class: "pk-radio",
12
+ data: {
13
+ phlex_kit__form_field_target: "input",
14
+ action: "change->phlex-kit--form-field#onInput invalid->phlex-kit--form-field#onInvalid"
15
+ }
16
+ }, @attrs))
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ /* Co-located with radio_group.rb — UI::RadioGroup, shadcn parity (grid gap-3). */
2
+ .pk-radio-group { display: grid; gap: .75rem; }
3
+ .pk-radio-group .pk-label { gap: .75rem; }
@@ -0,0 +1,14 @@
1
+ module PhlexKit
2
+ # Radio list wrapper, ported from shadcn/ui's RadioGroup: a role=radiogroup
3
+ # grid of (RadioButton + Label) rows sharing a `name`. The kit's RadioButton
4
+ # is the item. `.pk-radio-group` (radio_group.css).
5
+ class RadioGroup < BaseComponent
6
+ def initialize(**attrs)
7
+ @attrs = attrs
8
+ end
9
+
10
+ def view_template(&)
11
+ div(**mix({ class: "pk-radio-group", role: "radiogroup" }, @attrs), &)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ /* Co-located with resizable_panel_group.rb — UI::Resizable, shadcn parity
2
+ (react-resizable-panels replaced by the phlex-kit--resizable controller). */
3
+ .pk-resizable-group { display: flex; width: 100%; height: 100%; }
4
+ .pk-resizable-group.vertical { flex-direction: column; }
5
+ .pk-resizable-panel { flex: 1 1 0; min-width: 0; min-height: 0; overflow: hidden; }
6
+ .pk-resizable-handle {
7
+ position: relative;
8
+ flex: none;
9
+ background: var(--pk-border);
10
+ }
11
+ .pk-resizable-group.horizontal > .pk-resizable-handle { width: 1px; cursor: col-resize; }
12
+ .pk-resizable-group.vertical > .pk-resizable-handle { height: 1px; cursor: row-resize; }
13
+ /* fat invisible hit area */
14
+ .pk-resizable-group.horizontal > .pk-resizable-handle::after {
15
+ content: ""; position: absolute; inset: 0 -4px;
16
+ }
17
+ .pk-resizable-group.vertical > .pk-resizable-handle::after {
18
+ content: ""; position: absolute; inset: -4px 0;
19
+ }
20
+ .pk-resizable-handle:focus-visible {
21
+ outline: none;
22
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--pk-ring) 50%, transparent);
23
+ }
@@ -0,0 +1,21 @@
1
+ module PhlexKit
2
+ # Drag handle between two ResizablePanels. See resizable_panel_group.rb.
3
+ class ResizableHandle < BaseComponent
4
+ def initialize(**attrs)
5
+ @attrs = attrs
6
+ end
7
+
8
+ def view_template
9
+ div(**mix({
10
+ class: "pk-resizable-handle",
11
+ role: "separator",
12
+ tabindex: "0",
13
+ aria: { orientation: "vertical" },
14
+ data: {
15
+ phlex_kit__resizable_target: "handle",
16
+ action: "pointerdown->phlex-kit--resizable#start"
17
+ }
18
+ }, @attrs))
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module PhlexKit
2
+ # One pane of a ResizablePanelGroup. `default_size:` is a percentage.
3
+ # See resizable_panel_group.rb.
4
+ class ResizablePanel < BaseComponent
5
+ def initialize(default_size: nil, **attrs)
6
+ @default_size = default_size
7
+ @attrs = attrs
8
+ end
9
+
10
+ def view_template(&)
11
+ style = @default_size ? "flex-grow: #{@default_size}" : nil
12
+ div(**mix({
13
+ class: "pk-resizable-panel",
14
+ style: style,
15
+ data: { phlex_kit__resizable_target: "panel" }
16
+ }.compact, @attrs), &)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module PhlexKit
2
+ # Resizable split panels, ported from shadcn/ui's Resizable. The
3
+ # react-resizable-panels dependency is replaced by the phlex-kit--resizable
4
+ # controller: dragging a ResizableHandle rebalances the flex-grow of its
5
+ # neighbouring ResizablePanels. Compose ResizablePanelGroup(direction:) >
6
+ # ResizablePanel + ResizableHandle + ResizablePanel…
7
+ # `.pk-resizable*` (resizable.css).
8
+ class ResizablePanelGroup < BaseComponent
9
+ DIRECTIONS = { horizontal: "horizontal", vertical: "vertical" }.freeze
10
+
11
+ def initialize(direction: :horizontal, **attrs)
12
+ @direction = DIRECTIONS.fetch(direction.to_sym)
13
+ @attrs = attrs
14
+ end
15
+
16
+ def view_template(&)
17
+ div(**mix({
18
+ class: "pk-resizable-group #{@direction}",
19
+ data: {
20
+ controller: "phlex-kit--resizable",
21
+ phlex_kit__resizable_direction_value: @direction
22
+ }
23
+ }, @attrs), &)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ /* Co-located with scroll_area.rb — UI::ScrollArea: native scrolling with
2
+ shadcn-look thin themed scrollbars (no Radix/JS). */
3
+ .pk-scroll-area {
4
+ position: relative;
5
+ overflow: auto;
6
+ border-radius: inherit;
7
+ scrollbar-width: thin;
8
+ scrollbar-color: var(--pk-border) transparent;
9
+ }
10
+ .pk-scroll-area:focus-visible {
11
+ outline: none;
12
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
13
+ }
14
+ .pk-scroll-area::-webkit-scrollbar { width: .625rem; height: .625rem; }
15
+ .pk-scroll-area::-webkit-scrollbar-thumb {
16
+ background: var(--pk-border);
17
+ border-radius: 9999px;
18
+ border: 3px solid transparent;
19
+ background-clip: content-box;
20
+ }
21
+ .pk-scroll-area::-webkit-scrollbar-track { background: transparent; }
@@ -0,0 +1,15 @@
1
+ module PhlexKit
2
+ # Styled scroll container, ported from shadcn/ui's ScrollArea. Radix's custom
3
+ # scrollbars are replaced with native thin scrollbars themed via CSS — no JS.
4
+ # Constrain it with a height/width from the caller. `.pk-scroll-area`
5
+ # (scroll_area.css).
6
+ class ScrollArea < BaseComponent
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&)
12
+ div(**mix({ class: "pk-scroll-area", tabindex: "0" }, @attrs), &)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,80 @@
1
+ /* Co-located with select.rb — UI::Select (the custom JS dropdown; NativeSelect
2
+ is the native one). ruby_ui's structure + Stimulus kept; Tailwind replaced with
3
+ our vanilla CSS on the palette tokens. Geometry from ruby_ui (h-9 trigger,
4
+ rounded-md, p-1 viewport, px-2 py-1.5 items, accent on hover/selected). The
5
+ panel is positioned with plain CSS (top:100%) since we dropped @floating-ui.
6
+ Theme tokens come from the global stylesheet. */
7
+ .pk-select { position: relative; width: 100%; }
8
+
9
+ /* Hidden input that carries the form value. */
10
+ .pk-select-input { display: none; }
11
+
12
+ /* Closed-state trigger button (mirrors .pk-input geometry). */
13
+ .pk-select-trigger {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ width: 100%;
18
+ height: 2rem;
19
+ white-space: nowrap;
20
+ border-radius: var(--pk-radius);
21
+ border: 1px solid var(--pk-input);
22
+ background: transparent;
23
+ color: var(--pk-text);
24
+ padding: .25rem .75rem;
25
+ font: inherit;
26
+ font-size: .875rem;
27
+ cursor: pointer;
28
+ transition: border-color .15s ease, box-shadow .15s ease;
29
+ }
30
+ .pk-select-trigger:focus-visible {
31
+ outline: none;
32
+ border-color: var(--pk-brand);
33
+ box-shadow: 0 0 0 3px color-mix(in oklab, var(--pk-ring) 50%, transparent);
34
+ }
35
+ .pk-select-trigger:disabled { cursor: not-allowed; opacity: .5; }
36
+ .pk-select-trigger-icon { flex: none; width: 1rem; height: 1rem; margin-left: .5rem; opacity: .5; }
37
+ .pk-select-value { overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
38
+
39
+ /* Dropdown panel — opens directly below the trigger (no floating-ui). */
40
+ .pk-select-content {
41
+ position: absolute;
42
+ top: 100%;
43
+ left: 0;
44
+ z-index: 50;
45
+ width: 100%;
46
+ margin-top: .25rem;
47
+ }
48
+ .pk-select-content.hidden { display: none; }
49
+ .pk-select-viewport {
50
+ max-height: 24rem;
51
+ overflow: auto;
52
+ border-radius: calc(var(--pk-radius) - 2px);
53
+ border: 1px solid var(--pk-border);
54
+ background: var(--pk-surface);
55
+ color: var(--pk-text);
56
+ padding: .25rem;
57
+ box-shadow: 0 4px 16px rgba(0, 0, 0, .4);
58
+ }
59
+
60
+ .pk-select-label { margin: 0; padding: .375rem .5rem; font-size: .875rem; font-weight: 600; }
61
+
62
+ .pk-select-item {
63
+ position: relative;
64
+ display: flex;
65
+ align-items: center;
66
+ cursor: pointer;
67
+ user-select: none;
68
+ border-radius: calc(var(--pk-radius) - 4px);
69
+ padding: .375rem .5rem;
70
+ font-size: .875rem;
71
+ color: var(--pk-text);
72
+ outline: none;
73
+ transition: background-color .12s ease, color .12s ease;
74
+ }
75
+ .pk-select-item:hover,
76
+ .pk-select-item:focus,
77
+ .pk-select-item[aria-current="true"],
78
+ .pk-select-item[aria-selected="true"] { background: var(--pk-accent); }
79
+ .pk-select-item-check { visibility: hidden; flex: none; width: 1rem; height: 1rem; margin-right: .5rem; }
80
+ .pk-select-item[aria-selected="true"] .pk-select-item-check { visibility: visible; }
@@ -0,0 +1,43 @@
1
+ module PhlexKit
2
+ # Custom dropdown select, ported from ruby_ui's RubyUI::Select — the styled
3
+ # popover (Image #2), NOT the native <select> (that's PhlexKit::NativeSelect). Unlike
4
+ # the rest of the kit this component IS JS-driven: it keeps ruby_ui's Stimulus
5
+ # wiring (`phlex-kit--select` / `phlex-kit--select-item`, in
6
+ # app/javascript/controllers/ruby_ui/), since the open/close, selection, and
7
+ # keyboard nav are the point. The one change from upstream: the controller drops
8
+ # the `@floating-ui/dom` dependency and positions the panel with plain CSS
9
+ # (opens directly below the trigger). Tailwind → vanilla `.pk-select-*` (select.css).
10
+ #
11
+ # Multi-part. The hidden SelectInput carries the form value/name; SelectTrigger +
12
+ # SelectValue are the closed-state button; SelectContent > SelectGroup >
13
+ # (SelectLabel +) SelectItem are the panel:
14
+ #
15
+ # render PhlexKit::Select.new do
16
+ # render PhlexKit::SelectInput.new(name: "user[role]", id: "user_role", value: @user.role)
17
+ # render PhlexKit::SelectTrigger.new do
18
+ # render PhlexKit::SelectValue.new(placeholder: "Select a role") { @user.role&.capitalize }
19
+ # end
20
+ # render PhlexKit::SelectContent.new do
21
+ # render PhlexKit::SelectGroup.new do
22
+ # render PhlexKit::SelectItem.new(value: "admin", "aria-selected": "true") { "Admin" }
23
+ # end
24
+ # end
25
+ # end
26
+ class Select < BaseComponent
27
+ def initialize(**attrs)
28
+ @attrs = attrs
29
+ end
30
+
31
+ def view_template(&block)
32
+ div(**mix({
33
+ class: "pk-select",
34
+ data: {
35
+ controller: "phlex-kit--select",
36
+ phlex_kit__select_open_value: "false",
37
+ action: "click@window->phlex-kit--select#clickOutside",
38
+ phlex_kit__select_phlex_kit__select_item_outlet: ".pk-select-item"
39
+ }
40
+ }, @attrs), &block)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,24 @@
1
+ module PhlexKit
2
+ # The dropdown panel for PhlexKit::Select — hidden until the trigger opens it (the
3
+ # controller toggles the `hidden` class). Outer div is the positioned/targeted
4
+ # layer; the inner `.pk-select-viewport` is the bordered, scrollable box.
5
+ # Holds SelectGroup / SelectLabel / SelectItem children. See select.rb.
6
+ class SelectContent < BaseComponent
7
+ def initialize(**attrs)
8
+ @id = "pk-select-content-#{SecureRandom.hex(4)}"
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template(&block)
13
+ div(**mix({
14
+ id: @id,
15
+ role: "listbox",
16
+ tabindex: "-1",
17
+ class: "pk-select-content hidden",
18
+ data: { phlex_kit__select_target: "content" }
19
+ }, @attrs)) do
20
+ div(class: "pk-select-viewport", &block)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module PhlexKit
2
+ # Groups a set of SelectItems (optionally under a SelectLabel) inside a
3
+ # PhlexKit::SelectContent. Plain pass-through wrapper. See select.rb.
4
+ class SelectGroup < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&block)
10
+ div(**mix({ class: "pk-select-group" }, @attrs), &block)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module PhlexKit
2
+ # The hidden input that actually carries PhlexKit::Select's value into the form — the
3
+ # Stimulus controller writes the chosen item's value here and dispatches a
4
+ # `change` (which also feeds phlex-kit--form-field validation when the select
5
+ # sits inside a PhlexKit::FormField). Pass `name:`/`id:`/`value:` like any
6
+ # input; it's display:none. See select.rb.
7
+ class SelectInput < BaseComponent
8
+ def initialize(**attrs)
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template
13
+ input(**mix({
14
+ class: "pk-select-input",
15
+ data: {
16
+ phlex_kit__select_target: "input",
17
+ phlex_kit__form_field_target: "input",
18
+ action: "change->phlex-kit--form-field#onChange invalid->phlex-kit--form-field#onInvalid"
19
+ }
20
+ }, @attrs))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ module PhlexKit
2
+ # A selectable row in a PhlexKit::Select panel. Carries its `value:` in data-value; on
3
+ # click/enter the controller copies it into the hidden SelectInput, updates the
4
+ # SelectValue text, and flips `aria-selected` (which reveals the checkmark).
5
+ # Pass `selected: true` to mark the initially-chosen item server-side — it's a
6
+ # named kwarg (not an `"aria-selected":` attr) because `mix` would *merge* a
7
+ # repeated attribute rather than override it.
8
+ # See select.rb.
9
+ class SelectItem < BaseComponent
10
+ def initialize(value: nil, selected: false, **attrs)
11
+ @value = value
12
+ @selected = selected
13
+ @attrs = attrs
14
+ end
15
+
16
+ def view_template(&block)
17
+ div(**mix({
18
+ role: "option",
19
+ tabindex: "0",
20
+ class: "pk-select-item",
21
+ "aria-selected": (@selected ? "true" : "false"),
22
+ data: {
23
+ value: @value,
24
+ controller: "phlex-kit--select-item",
25
+ action: "click->phlex-kit--select#selectItem keydown.enter->phlex-kit--select#selectItem " \
26
+ "keydown.down->phlex-kit--select#handleKeyDown keydown.up->phlex-kit--select#handleKeyUp " \
27
+ "keydown.esc->phlex-kit--select#handleEsc",
28
+ phlex_kit__select_target: "item"
29
+ }
30
+ }, @attrs)) do
31
+ check_icon
32
+ block&.call
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def check_icon
39
+ svg(
40
+ xmlns: "http://www.w3.org/2000/svg",
41
+ viewbox: "0 0 24 24",
42
+ fill: "none",
43
+ stroke: "currentColor",
44
+ "stroke-width": "2",
45
+ "stroke-linecap": "round",
46
+ "stroke-linejoin": "round",
47
+ class: "pk-select-item-check",
48
+ "aria-hidden": "true"
49
+ ) { |s| s.path(d: "M20 6 9 17l-5-5") }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ module PhlexKit
2
+ # A heading labelling a SelectGroup inside PhlexKit::SelectContent (e.g. "Fruits").
3
+ # See select.rb.
4
+ class SelectLabel < BaseComponent
5
+ def initialize(**attrs)
6
+ @attrs = attrs
7
+ end
8
+
9
+ def view_template(&block)
10
+ h3(**mix({ class: "pk-select-label" }, @attrs), &block)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ module PhlexKit
2
+ # The closed-state button for PhlexKit::Select — shows the current SelectValue and a
3
+ # up/down chevron, and opens the panel on click (`phlex-kit--select#onClick`).
4
+ # See select.rb.
5
+ class SelectTrigger < BaseComponent
6
+ def initialize(**attrs)
7
+ @attrs = attrs
8
+ end
9
+
10
+ def view_template(&block)
11
+ button(**mix({
12
+ type: :button,
13
+ role: "combobox",
14
+ class: "pk-select-trigger",
15
+ aria: { expanded: "false", haspopup: "listbox", autocomplete: "none" },
16
+ data: { action: "phlex-kit--select#onClick", phlex_kit__select_target: "trigger" }
17
+ }, @attrs)) do
18
+ block&.call
19
+ icon
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def icon
26
+ svg(
27
+ xmlns: "http://www.w3.org/2000/svg",
28
+ viewbox: "0 0 24 24",
29
+ fill: "none",
30
+ stroke: "currentColor",
31
+ "stroke-width": "2",
32
+ "stroke-linecap": "round",
33
+ "stroke-linejoin": "round",
34
+ class: "pk-select-trigger-icon",
35
+ "aria-hidden": "true"
36
+ ) do |s|
37
+ s.path(d: "m7 15 5 5 5-5")
38
+ s.path(d: "m7 9 5-5 5 5")
39
+ end
40
+ end
41
+ end
42
+ end