poetry-ui 0.0.2
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.
- checksums.yaml +7 -0
- data/.gitattributes +6 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/THIRD_PARTY_NOTICES.md +201 -0
- data/app/components/poetry/ui/accordion/component.html.erb +3 -0
- data/app/components/poetry/ui/accordion/component.rb +197 -0
- data/app/components/poetry/ui/accordion/preview.rb +39 -0
- data/app/components/poetry/ui/accordion/style.rb +37 -0
- data/app/components/poetry/ui/alert/component.html.erb +6 -0
- data/app/components/poetry/ui/alert/component.rb +61 -0
- data/app/components/poetry/ui/alert/preview.rb +35 -0
- data/app/components/poetry/ui/alert/style.rb +24 -0
- data/app/components/poetry/ui/alert_dialog/component.html.erb +17 -0
- data/app/components/poetry/ui/alert_dialog/component.rb +208 -0
- data/app/components/poetry/ui/alert_dialog/preview.rb +48 -0
- data/app/components/poetry/ui/alert_dialog/style.rb +46 -0
- data/app/components/poetry/ui/aspect_ratio/component.rb +63 -0
- data/app/components/poetry/ui/aspect_ratio/preview.rb +29 -0
- data/app/components/poetry/ui/aspect_ratio/style.rb +13 -0
- data/app/components/poetry/ui/attachment/component.html.erb +13 -0
- data/app/components/poetry/ui/attachment/component.rb +139 -0
- data/app/components/poetry/ui/attachment/preview.rb +83 -0
- data/app/components/poetry/ui/attachment/style.rb +59 -0
- data/app/components/poetry/ui/autocomplete/component.html.erb +16 -0
- data/app/components/poetry/ui/autocomplete/component.rb +193 -0
- data/app/components/poetry/ui/autocomplete/preview.rb +39 -0
- data/app/components/poetry/ui/autocomplete/style.rb +18 -0
- data/app/components/poetry/ui/avatar/component.rb +103 -0
- data/app/components/poetry/ui/avatar/group_preview.html.erb +11 -0
- data/app/components/poetry/ui/avatar/preview.rb +50 -0
- data/app/components/poetry/ui/avatar/style.rb +34 -0
- data/app/components/poetry/ui/badge/component.rb +73 -0
- data/app/components/poetry/ui/badge/preview.rb +54 -0
- data/app/components/poetry/ui/badge/style.rb +35 -0
- data/app/components/poetry/ui/breadcrumb/component.html.erb +32 -0
- data/app/components/poetry/ui/breadcrumb/component.rb +102 -0
- data/app/components/poetry/ui/breadcrumb/dropdown_crumb_preview.html.erb +21 -0
- data/app/components/poetry/ui/breadcrumb/preview.rb +45 -0
- data/app/components/poetry/ui/breadcrumb/style.rb +25 -0
- data/app/components/poetry/ui/bubble/component.html.erb +4 -0
- data/app/components/poetry/ui/bubble/component.rb +106 -0
- data/app/components/poetry/ui/bubble/preview.rb +62 -0
- data/app/components/poetry/ui/bubble/style.rb +47 -0
- data/app/components/poetry/ui/button/component.html.erb +14 -0
- data/app/components/poetry/ui/button/component.rb +168 -0
- data/app/components/poetry/ui/button/preview.rb +94 -0
- data/app/components/poetry/ui/button/style.rb +41 -0
- data/app/components/poetry/ui/button_group/component.rb +70 -0
- data/app/components/poetry/ui/button_group/popup_triggers_preview.html.erb +21 -0
- data/app/components/poetry/ui/button_group/preview.rb +46 -0
- data/app/components/poetry/ui/button_group/style.rb +27 -0
- data/app/components/poetry/ui/button_group/text_prefix_preview.html.erb +7 -0
- data/app/components/poetry/ui/calendar/component.html.erb +72 -0
- data/app/components/poetry/ui/calendar/component.rb +366 -0
- data/app/components/poetry/ui/calendar/preview.rb +55 -0
- data/app/components/poetry/ui/calendar/style.rb +55 -0
- data/app/components/poetry/ui/card/component.html.erb +11 -0
- data/app/components/poetry/ui/card/component.rb +90 -0
- data/app/components/poetry/ui/card/preview.rb +29 -0
- data/app/components/poetry/ui/card/style.rb +23 -0
- data/app/components/poetry/ui/carousel/component.html.erb +17 -0
- data/app/components/poetry/ui/carousel/component.rb +179 -0
- data/app/components/poetry/ui/carousel/preview.rb +67 -0
- data/app/components/poetry/ui/carousel/style.rb +39 -0
- data/app/components/poetry/ui/checkbox/component.html.erb +22 -0
- data/app/components/poetry/ui/checkbox/component.rb +179 -0
- data/app/components/poetry/ui/checkbox/preview.rb +68 -0
- data/app/components/poetry/ui/checkbox/style.rb +28 -0
- data/app/components/poetry/ui/clipboard_text/component.html.erb +15 -0
- data/app/components/poetry/ui/clipboard_text/component.rb +154 -0
- data/app/components/poetry/ui/clipboard_text/preview.rb +24 -0
- data/app/components/poetry/ui/clipboard_text/style.rb +23 -0
- data/app/components/poetry/ui/code_block/component.html.erb +13 -0
- data/app/components/poetry/ui/code_block/component.rb +141 -0
- data/app/components/poetry/ui/code_block/preview.rb +40 -0
- data/app/components/poetry/ui/code_block/style.rb +38 -0
- data/app/components/poetry/ui/collapsible/component.html.erb +4 -0
- data/app/components/poetry/ui/collapsible/component.rb +134 -0
- data/app/components/poetry/ui/collapsible/preview.rb +24 -0
- data/app/components/poetry/ui/combobox/component.html.erb +49 -0
- data/app/components/poetry/ui/combobox/component.rb +1086 -0
- data/app/components/poetry/ui/combobox/preview.rb +164 -0
- data/app/components/poetry/ui/combobox/style.rb +148 -0
- data/app/components/poetry/ui/command/component.html.erb +20 -0
- data/app/components/poetry/ui/command/component.rb +526 -0
- data/app/components/poetry/ui/command/dialog_component.html.erb +14 -0
- data/app/components/poetry/ui/command/dialog_component.rb +206 -0
- data/app/components/poetry/ui/command/dialog_preview.rb +39 -0
- data/app/components/poetry/ui/command/preview.rb +121 -0
- data/app/components/poetry/ui/command/style.rb +78 -0
- data/app/components/poetry/ui/composable_trigger.rb +63 -0
- data/app/components/poetry/ui/context_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/context_menu/component.rb +358 -0
- data/app/components/poetry/ui/context_menu/preview.rb +111 -0
- data/app/components/poetry/ui/context_menu/style.rb +59 -0
- data/app/components/poetry/ui/data_table/component.html.erb +78 -0
- data/app/components/poetry/ui/data_table/component.rb +277 -0
- data/app/components/poetry/ui/data_table/preview.rb +68 -0
- data/app/components/poetry/ui/data_table/state.rb +115 -0
- data/app/components/poetry/ui/data_table/style.rb +42 -0
- data/app/components/poetry/ui/date_field/component.html.erb +7 -0
- data/app/components/poetry/ui/date_field/component.rb +209 -0
- data/app/components/poetry/ui/date_field/preview.rb +42 -0
- data/app/components/poetry/ui/date_field/style.rb +35 -0
- data/app/components/poetry/ui/date_picker/component.html.erb +23 -0
- data/app/components/poetry/ui/date_picker/component.rb +242 -0
- data/app/components/poetry/ui/date_picker/preview.rb +31 -0
- data/app/components/poetry/ui/date_picker/style.rb +25 -0
- data/app/components/poetry/ui/deferred/component.html.erb +16 -0
- data/app/components/poetry/ui/deferred/component.rb +90 -0
- data/app/components/poetry/ui/deferred/preview.rb +24 -0
- data/app/components/poetry/ui/dialog/component.html.erb +21 -0
- data/app/components/poetry/ui/dialog/component.rb +194 -0
- data/app/components/poetry/ui/dialog/preview.rb +33 -0
- data/app/components/poetry/ui/dialog/style.rb +30 -0
- data/app/components/poetry/ui/drawer/component.html.erb +16 -0
- data/app/components/poetry/ui/drawer/component.rb +207 -0
- data/app/components/poetry/ui/drawer/preview.rb +69 -0
- data/app/components/poetry/ui/drawer/style.rb +105 -0
- data/app/components/poetry/ui/dropdown_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/dropdown_menu/component.rb +350 -0
- data/app/components/poetry/ui/dropdown_menu/preview.rb +101 -0
- data/app/components/poetry/ui/dropdown_menu/style.rb +60 -0
- data/app/components/poetry/ui/empty/component.html.erb +10 -0
- data/app/components/poetry/ui/empty/component.rb +79 -0
- data/app/components/poetry/ui/empty/preview.rb +27 -0
- data/app/components/poetry/ui/empty/style.rb +27 -0
- data/app/components/poetry/ui/family_identity.rb +52 -0
- data/app/components/poetry/ui/field/component.html.erb +19 -0
- data/app/components/poetry/ui/field/component.rb +183 -0
- data/app/components/poetry/ui/field/preview.html.erb +30 -0
- data/app/components/poetry/ui/field/preview.rb +72 -0
- data/app/components/poetry/ui/field/style.rb +68 -0
- data/app/components/poetry/ui/field_group/component.rb +61 -0
- data/app/components/poetry/ui/field_group/preview.html.erb +30 -0
- data/app/components/poetry/ui/field_group/preview.rb +20 -0
- data/app/components/poetry/ui/field_group/style.rb +25 -0
- data/app/components/poetry/ui/field_separator/component.rb +70 -0
- data/app/components/poetry/ui/field_separator/preview.rb +18 -0
- data/app/components/poetry/ui/field_separator/style.rb +20 -0
- data/app/components/poetry/ui/fieldset/component.rb +90 -0
- data/app/components/poetry/ui/fieldset/preview.html.erb +22 -0
- data/app/components/poetry/ui/fieldset/preview.rb +25 -0
- data/app/components/poetry/ui/fieldset/style.rb +18 -0
- data/app/components/poetry/ui/file_input/component.html.erb +14 -0
- data/app/components/poetry/ui/file_input/component.rb +188 -0
- data/app/components/poetry/ui/file_input/preview.rb +30 -0
- data/app/components/poetry/ui/file_input/style.rb +35 -0
- data/app/components/poetry/ui/hover_card/component.html.erb +6 -0
- data/app/components/poetry/ui/hover_card/component.rb +201 -0
- data/app/components/poetry/ui/hover_card/preview.rb +60 -0
- data/app/components/poetry/ui/hover_card/style.rb +17 -0
- data/app/components/poetry/ui/icon/component.rb +127 -0
- data/app/components/poetry/ui/icon/preview.rb +21 -0
- data/app/components/poetry/ui/input/component.rb +80 -0
- data/app/components/poetry/ui/input/preview.rb +35 -0
- data/app/components/poetry/ui/input/style.rb +18 -0
- data/app/components/poetry/ui/input_group/button_preview.html.erb +12 -0
- data/app/components/poetry/ui/input_group/component.rb +69 -0
- data/app/components/poetry/ui/input_group/preview.rb +23 -0
- data/app/components/poetry/ui/input_group/search_preview.html.erb +13 -0
- data/app/components/poetry/ui/input_group/style.rb +40 -0
- data/app/components/poetry/ui/input_group/textarea_preview.html.erb +10 -0
- data/app/components/poetry/ui/input_group_field.rb +61 -0
- data/app/components/poetry/ui/input_otp/component.html.erb +25 -0
- data/app/components/poetry/ui/input_otp/component.rb +250 -0
- data/app/components/poetry/ui/input_otp/preview.rb +91 -0
- data/app/components/poetry/ui/input_otp/style.rb +36 -0
- data/app/components/poetry/ui/item/component.html.erb +13 -0
- data/app/components/poetry/ui/item/component.rb +99 -0
- data/app/components/poetry/ui/item/group_preview.html.erb +15 -0
- data/app/components/poetry/ui/item/preview.rb +52 -0
- data/app/components/poetry/ui/item/style.rb +45 -0
- data/app/components/poetry/ui/kbd/component.rb +47 -0
- data/app/components/poetry/ui/kbd/group_preview.html.erb +4 -0
- data/app/components/poetry/ui/kbd/preview.rb +23 -0
- data/app/components/poetry/ui/kbd/style.rb +13 -0
- data/app/components/poetry/ui/label/component.rb +46 -0
- data/app/components/poetry/ui/label/preview.html.erb +5 -0
- data/app/components/poetry/ui/label/preview.rb +16 -0
- data/app/components/poetry/ui/label/style.rb +13 -0
- data/app/components/poetry/ui/link/component.rb +62 -0
- data/app/components/poetry/ui/link/preview.rb +31 -0
- data/app/components/poetry/ui/link/style.rb +20 -0
- data/app/components/poetry/ui/marker/component.html.erb +4 -0
- data/app/components/poetry/ui/marker/component.rb +81 -0
- data/app/components/poetry/ui/marker/preview.rb +43 -0
- data/app/components/poetry/ui/marker/style.rb +24 -0
- data/app/components/poetry/ui/menubar/component.html.erb +3 -0
- data/app/components/poetry/ui/menubar/component.rb +493 -0
- data/app/components/poetry/ui/menubar/preview.rb +164 -0
- data/app/components/poetry/ui/menubar/style.rb +71 -0
- data/app/components/poetry/ui/menus.rb +386 -0
- data/app/components/poetry/ui/message/component.html.erb +8 -0
- data/app/components/poetry/ui/message/component.rb +64 -0
- data/app/components/poetry/ui/message/preview.rb +33 -0
- data/app/components/poetry/ui/message/style.rb +29 -0
- data/app/components/poetry/ui/message_scroller/component.html.erb +15 -0
- data/app/components/poetry/ui/message_scroller/component.rb +163 -0
- data/app/components/poetry/ui/message_scroller/preview.rb +33 -0
- data/app/components/poetry/ui/message_scroller/style.rb +39 -0
- data/app/components/poetry/ui/metadata_list/component.rb +88 -0
- data/app/components/poetry/ui/metadata_list/preview.rb +55 -0
- data/app/components/poetry/ui/metadata_list/style.rb +35 -0
- data/app/components/poetry/ui/meter/component.rb +117 -0
- data/app/components/poetry/ui/meter/preview.rb +28 -0
- data/app/components/poetry/ui/meter/style.rb +16 -0
- data/app/components/poetry/ui/native_select/component.rb +124 -0
- data/app/components/poetry/ui/native_select/labelled_preview.html.erb +7 -0
- data/app/components/poetry/ui/native_select/preview.rb +20 -0
- data/app/components/poetry/ui/native_select/style.rb +22 -0
- data/app/components/poetry/ui/navigation_menu/bar_preview.html.erb +14 -0
- data/app/components/poetry/ui/navigation_menu/component.html.erb +25 -0
- data/app/components/poetry/ui/navigation_menu/component.rb +254 -0
- data/app/components/poetry/ui/navigation_menu/preview.rb +20 -0
- data/app/components/poetry/ui/navigation_menu/style.rb +73 -0
- data/app/components/poetry/ui/navigation_menu/viewport_preview.html.erb +23 -0
- data/app/components/poetry/ui/number_field/component.html.erb +19 -0
- data/app/components/poetry/ui/number_field/component.rb +263 -0
- data/app/components/poetry/ui/number_field/preview.rb +66 -0
- data/app/components/poetry/ui/number_field/style.rb +14 -0
- data/app/components/poetry/ui/pagination/component.html.erb +40 -0
- data/app/components/poetry/ui/pagination/component.rb +182 -0
- data/app/components/poetry/ui/pagination/preview.rb +47 -0
- data/app/components/poetry/ui/pagination/style.rb +27 -0
- data/app/components/poetry/ui/popover/component.html.erb +17 -0
- data/app/components/poetry/ui/popover/component.rb +203 -0
- data/app/components/poetry/ui/popover/preview.html.erb +29 -0
- data/app/components/poetry/ui/popover/preview.rb +66 -0
- data/app/components/poetry/ui/popover/style.rb +26 -0
- data/app/components/poetry/ui/popper_consumer.rb +69 -0
- data/app/components/poetry/ui/progress/component.rb +95 -0
- data/app/components/poetry/ui/progress/preview.rb +22 -0
- data/app/components/poetry/ui/progress/style.rb +19 -0
- data/app/components/poetry/ui/questionnaire/component.html.erb +80 -0
- data/app/components/poetry/ui/questionnaire/component.rb +396 -0
- data/app/components/poetry/ui/questionnaire/preview.rb +99 -0
- data/app/components/poetry/ui/questionnaire/style.rb +53 -0
- data/app/components/poetry/ui/radio_group/component.html.erb +4 -0
- data/app/components/poetry/ui/radio_group/component.rb +323 -0
- data/app/components/poetry/ui/radio_group/preview.rb +109 -0
- data/app/components/poetry/ui/radio_group/style.rb +53 -0
- data/app/components/poetry/ui/resizable/component.html.erb +10 -0
- data/app/components/poetry/ui/resizable/component.rb +178 -0
- data/app/components/poetry/ui/resizable/preview.rb +42 -0
- data/app/components/poetry/ui/resizable/style.rb +30 -0
- data/app/components/poetry/ui/scroll_area/component.rb +76 -0
- data/app/components/poetry/ui/scroll_area/preview.rb +35 -0
- data/app/components/poetry/ui/scroll_area/style.rb +19 -0
- data/app/components/poetry/ui/search_field/component.html.erb +15 -0
- data/app/components/poetry/ui/search_field/component.rb +137 -0
- data/app/components/poetry/ui/search_field/preview.rb +23 -0
- data/app/components/poetry/ui/search_field/style.rb +18 -0
- data/app/components/poetry/ui/select/component.html.erb +15 -0
- data/app/components/poetry/ui/select/component.rb +639 -0
- data/app/components/poetry/ui/select/preview.rb +113 -0
- data/app/components/poetry/ui/select/style.rb +58 -0
- data/app/components/poetry/ui/sensitive_input/component.html.erb +29 -0
- data/app/components/poetry/ui/sensitive_input/component.rb +274 -0
- data/app/components/poetry/ui/sensitive_input/preview.rb +40 -0
- data/app/components/poetry/ui/sensitive_input/style.rb +49 -0
- data/app/components/poetry/ui/separator/component.rb +57 -0
- data/app/components/poetry/ui/separator/preview.rb +19 -0
- data/app/components/poetry/ui/separator/style.rb +15 -0
- data/app/components/poetry/ui/sheet/component.html.erb +21 -0
- data/app/components/poetry/ui/sheet/component.rb +97 -0
- data/app/components/poetry/ui/sheet/preview.rb +57 -0
- data/app/components/poetry/ui/sheet/style.rb +47 -0
- data/app/components/poetry/ui/sidebar/component.html.erb +20 -0
- data/app/components/poetry/ui/sidebar/component.rb +249 -0
- data/app/components/poetry/ui/sidebar/preview.rb +21 -0
- data/app/components/poetry/ui/sidebar/shell_preview.html.erb +47 -0
- data/app/components/poetry/ui/sidebar/style.rb +139 -0
- data/app/components/poetry/ui/skeleton/component.rb +39 -0
- data/app/components/poetry/ui/skeleton/preview.rb +22 -0
- data/app/components/poetry/ui/skeleton/style.rb +13 -0
- data/app/components/poetry/ui/slider/component.html.erb +13 -0
- data/app/components/poetry/ui/slider/component.rb +360 -0
- data/app/components/poetry/ui/slider/preview.rb +73 -0
- data/app/components/poetry/ui/slider/style.rb +55 -0
- data/app/components/poetry/ui/spinner/component.rb +58 -0
- data/app/components/poetry/ui/spinner/preview.rb +22 -0
- data/app/components/poetry/ui/spinner/style.rb +12 -0
- data/app/components/poetry/ui/stat/component.html.erb +13 -0
- data/app/components/poetry/ui/stat/component.rb +102 -0
- data/app/components/poetry/ui/stat/preview.rb +47 -0
- data/app/components/poetry/ui/stat/style.rb +29 -0
- data/app/components/poetry/ui/switch/component.html.erb +15 -0
- data/app/components/poetry/ui/switch/component.rb +136 -0
- data/app/components/poetry/ui/switch/preview.rb +66 -0
- data/app/components/poetry/ui/switch/style.rb +22 -0
- data/app/components/poetry/ui/table/component.html.erb +3 -0
- data/app/components/poetry/ui/table/component.rb +93 -0
- data/app/components/poetry/ui/table/preview.rb +96 -0
- data/app/components/poetry/ui/table/style.rb +26 -0
- data/app/components/poetry/ui/tabs/component.html.erb +10 -0
- data/app/components/poetry/ui/tabs/component.rb +253 -0
- data/app/components/poetry/ui/tabs/preview.rb +43 -0
- data/app/components/poetry/ui/tabs/style.rb +49 -0
- data/app/components/poetry/ui/tag_group/component.html.erb +8 -0
- data/app/components/poetry/ui/tag_group/component.rb +203 -0
- data/app/components/poetry/ui/tag_group/preview.rb +43 -0
- data/app/components/poetry/ui/tag_group/style.rb +29 -0
- data/app/components/poetry/ui/textarea/component.rb +64 -0
- data/app/components/poetry/ui/textarea/preview.rb +75 -0
- data/app/components/poetry/ui/textarea/style.rb +18 -0
- data/app/components/poetry/ui/time_field/component.html.erb +6 -0
- data/app/components/poetry/ui/time_field/component.rb +85 -0
- data/app/components/poetry/ui/time_field/preview.rb +40 -0
- data/app/components/poetry/ui/time_field/style.rb +30 -0
- data/app/components/poetry/ui/timeline/component.rb +134 -0
- data/app/components/poetry/ui/timeline/preview.rb +61 -0
- data/app/components/poetry/ui/timeline/style.rb +54 -0
- data/app/components/poetry/ui/toast/component.html.erb +24 -0
- data/app/components/poetry/ui/toast/component.rb +178 -0
- data/app/components/poetry/ui/toast/preview.rb +69 -0
- data/app/components/poetry/ui/toast/style.rb +33 -0
- data/app/components/poetry/ui/toast_trigger/addressed_preview.html.erb +12 -0
- data/app/components/poetry/ui/toast_trigger/component.html.erb +3 -0
- data/app/components/poetry/ui/toast_trigger/component.rb +82 -0
- data/app/components/poetry/ui/toast_trigger/default_preview.html.erb +11 -0
- data/app/components/poetry/ui/toast_trigger/preview.rb +19 -0
- data/app/components/poetry/ui/toaster/component.html.erb +1 -0
- data/app/components/poetry/ui/toaster/component.rb +99 -0
- data/app/components/poetry/ui/toaster/positions_preview.html.erb +24 -0
- data/app/components/poetry/ui/toaster/preview.rb +46 -0
- data/app/components/poetry/ui/toaster/stamp_preview.html.erb +15 -0
- data/app/components/poetry/ui/toaster/style.rb +26 -0
- data/app/components/poetry/ui/toggle/component.rb +120 -0
- data/app/components/poetry/ui/toggle/preview.rb +79 -0
- data/app/components/poetry/ui/toggle/style.rb +33 -0
- data/app/components/poetry/ui/toggle_group/component.html.erb +4 -0
- data/app/components/poetry/ui/toggle_group/component.rb +252 -0
- data/app/components/poetry/ui/toggle_group/preview.rb +143 -0
- data/app/components/poetry/ui/toggle_group/style.rb +33 -0
- data/app/components/poetry/ui/toolbar/component.rb +125 -0
- data/app/components/poetry/ui/toolbar/preview.rb +41 -0
- data/app/components/poetry/ui/toolbar/style.rb +20 -0
- data/app/components/poetry/ui/tooltip/component.html.erb +20 -0
- data/app/components/poetry/ui/tooltip/component.rb +185 -0
- data/app/components/poetry/ui/tooltip/preview.rb +74 -0
- data/app/components/poetry/ui/tooltip/previews/provider_row.html.erb +25 -0
- data/app/components/poetry/ui/tooltip/style.rb +24 -0
- data/app/components/poetry/ui/tree/component.html.erb +32 -0
- data/app/components/poetry/ui/tree/component.rb +233 -0
- data/app/components/poetry/ui/tree/preview.rb +55 -0
- data/app/components/poetry/ui/tree/style.rb +29 -0
- data/app/components/poetry/ui/typeset/component.rb +73 -0
- data/app/components/poetry/ui/typeset/preview.rb +45 -0
- data/app/components/poetry/ui/typeset/style.rb +15 -0
- data/app/controllers/poetry/ui/llms_controller.rb +28 -0
- data/app/helpers/poetry/ui/components_helper.rb +1794 -0
- data/app/helpers/poetry/ui/form_builder/type_inference.rb +117 -0
- data/app/helpers/poetry/ui/form_builder.rb +916 -0
- data/app/helpers/poetry/ui/optimistic_form_builder.rb +55 -0
- data/app/helpers/poetry/ui/toast_stream_actions.rb +27 -0
- data/config/component_descriptions.yml +96 -0
- data/config/component_registry.yml +12257 -0
- data/config/locales/en.yml +93 -0
- data/config/routes.rb +8 -0
- data/config/template_classes.txt +113 -0
- data/lib/generators/poetry/add/add_generator.rb +218 -0
- data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +91 -0
- data/lib/generators/poetry/agents/agents_generator.rb +24 -0
- data/lib/generators/poetry/agents_section.rb +148 -0
- data/lib/generators/poetry/block/block_generator.rb +67 -0
- data/lib/generators/poetry/block/templates/action_bar.html.erb +45 -0
- data/lib/generators/poetry/block/templates/app_shell.html.erb +139 -0
- data/lib/generators/poetry/block/templates/data_index.html.erb +85 -0
- data/lib/generators/poetry/block/templates/destructive_panel.html.erb +26 -0
- data/lib/generators/poetry/block/templates/page_header.html.erb +25 -0
- data/lib/generators/poetry/block/templates/section_card.html.erb +42 -0
- data/lib/generators/poetry/block/templates/stepper.html.erb +79 -0
- data/lib/generators/poetry/block/templates/top_nav.html.erb +44 -0
- data/lib/generators/poetry/diff/diff_generator.rb +135 -0
- data/lib/generators/poetry/editor/editor_generator.rb +344 -0
- data/lib/generators/poetry/editor/templates/poetry-check-hook +81 -0
- data/lib/generators/poetry/install/install_generator.rb +364 -0
- data/lib/generators/poetry/pagination/pagination_generator.rb +76 -0
- data/lib/generators/poetry/pagination/templates/kaminari_paginator.html.erb +29 -0
- data/lib/generators/poetry/pagination/templates/poetry_link_renderer.rb +34 -0
- data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +28 -0
- data/lib/generators/poetry/recipes/agent_embed/app/javascript/controllers/agent_demo_controller.js +131 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/controllers/orders_controller.rb +15 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/views/orders/index.html.erb +7 -0
- data/lib/generators/poetry/recipes/screen_data_index/test/system/orders_index_test.rb +16 -0
- data/lib/generators/poetry/recipes/screen_settings/app/controllers/settings_controller.rb +14 -0
- data/lib/generators/poetry/recipes/screen_settings/app/views/settings/show.html.erb +9 -0
- data/lib/generators/poetry/recipes/screen_settings/test/system/settings_show_test.rb +16 -0
- data/lib/generators/poetry/scaffold_templates/scaffold_templates_generator.rb +60 -0
- data/lib/generators/poetry/scaffold_templates/templates/_form.html.erb.tt +77 -0
- data/lib/generators/poetry/scaffold_templates/templates/controller.rb.tt +78 -0
- data/lib/generators/poetry/scaffold_templates/templates/edit.html.erb.tt +14 -0
- data/lib/generators/poetry/scaffold_templates/templates/index.html.erb.tt +24 -0
- data/lib/generators/poetry/scaffold_templates/templates/new.html.erb.tt +11 -0
- data/lib/generators/poetry/scaffold_templates/templates/partial.html.erb.tt +13 -0
- data/lib/generators/poetry/scaffold_templates/templates/show.html.erb.tt +18 -0
- data/lib/generators/poetry/skill/skill_generator.rb +34 -0
- data/lib/generators/poetry/skill/templates/poetry-component/SKILL.md +58 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/anatomy.md +152 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/checklist.md +67 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/documentation.md +137 -0
- data/lib/generators/poetry/skill/templates/poetry-design/SKILL.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/audit.md +123 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/compose.md +73 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/figma.md +68 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/paper.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/study.md +47 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/theme.md +70 -0
- data/lib/generators/poetry/skills_section.rb +58 -0
- data/lib/poetry/ui/chat.rb +283 -0
- data/lib/poetry/ui/code_block_highlighter.rb +60 -0
- data/lib/poetry/ui/engine.rb +55 -0
- data/lib/poetry/ui/recipes.rb +157 -0
- data/lib/poetry/ui/testing/combobox.rb +167 -0
- data/lib/poetry/ui/testing/dialog.rb +73 -0
- data/lib/poetry/ui/testing/menu.rb +127 -0
- data/lib/poetry/ui/testing/registration.rb +58 -0
- data/lib/poetry/ui/testing/select.rb +147 -0
- data/lib/poetry/ui/testing/tester.rb +130 -0
- data/lib/poetry/ui/testing.rb +130 -0
- data/lib/poetry/ui/themes.rb +109 -0
- data/lib/poetry/ui/version.rb +8 -0
- data/lib/poetry/ui/webmcp.rb +91 -0
- data/lib/poetry/ui.rb +285 -0
- data/lib/poetry-ui.rb +5 -0
- data/lib/tasks/poetry/check.rake +73 -0
- data/lib/tasks/poetry/design.rake +293 -0
- data/lib/tasks/poetry/verify.rake +36 -0
- data/themes/default.css +1793 -0
- data/themes/luma.css +1764 -0
- data/themes/lyra.css +1768 -0
- data/themes/maia.css +1768 -0
- data/themes/mira.css +1760 -0
- data/themes/nova.css +1755 -0
- data/themes/rhea.css +1771 -0
- data/themes/sera.css +1757 -0
- data/themes/vega.css +1775 -0
- data/typeset/typeset.css +499 -0
- metadata +502 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<%# poetry:block title="Stepper" description="A multi-step flow header: numbered step indicators with connectors and completed/current/upcoming states over the current step's panel with back/continue actions." keywords="stepper, wizard, steps, checkout, onboarding, progress, multi-step" %>
|
|
2
|
+
<%# Stepper is a BLOCK, not a component: neither shadcn nor Base UI
|
|
3
|
+
ships one, so the pattern lives here as copy-in source - adapt the
|
|
4
|
+
steps freely; nothing hides behind an API. The server owns step state:
|
|
5
|
+
pass step: as a local, or let it ride ?step= - Back/Continue are plain
|
|
6
|
+
links that re-render, and completed steps are links back. In a real
|
|
7
|
+
flow, swap the query param for your wizard's own routes/model state
|
|
8
|
+
and the final action for a form submit. %>
|
|
9
|
+
<% steps = ["Shipping", "Payment", "Review"] %>
|
|
10
|
+
<% step = (local_assigns[:step] || params[:step] || 2).to_i.clamp(1, steps.length) %>
|
|
11
|
+
<% panels = {
|
|
12
|
+
1 => ["Shipping", "Step 1 of 3 - where this order lands.",
|
|
13
|
+
"Address fields mount here; this panel is the step's content slot."],
|
|
14
|
+
2 => ["Payment", "Step 2 of 3 - how this order gets paid.",
|
|
15
|
+
"Card details are collected by the payment element that mounts here; this panel is the step's content slot."],
|
|
16
|
+
3 => ["Review", "Step 3 of 3 - confirm and place the order.",
|
|
17
|
+
"The order summary renders here - line items, shipping choice, and the charge that Place order will submit."]
|
|
18
|
+
} %>
|
|
19
|
+
<% title, description, body = panels.fetch(step) %>
|
|
20
|
+
<div class="mx-auto max-w-2xl p-6">
|
|
21
|
+
<nav aria-label="Progress">
|
|
22
|
+
<ol class="flex items-center gap-3">
|
|
23
|
+
<% steps.each_with_index do |label, index| %>
|
|
24
|
+
<% number = index + 1 %>
|
|
25
|
+
<% unless number == 1 %>
|
|
26
|
+
<li aria-hidden="true" class="h-px min-w-8 flex-1 <%= number <= step ? "bg-primary" : "bg-border" %>"></li>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% if number < step %>
|
|
29
|
+
<%# Completed: filled indicator with a check, revisitable. %>
|
|
30
|
+
<li class="flex items-center">
|
|
31
|
+
<a href="?step=<%= number %>" class="flex items-center gap-3 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
32
|
+
<span class="flex size-8 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
|
33
|
+
<%= poetry_icon(name: :check, class: "size-4", label: "Completed") %>
|
|
34
|
+
</span>
|
|
35
|
+
<span class="text-sm font-medium"><%= label %></span>
|
|
36
|
+
</a>
|
|
37
|
+
</li>
|
|
38
|
+
<% elsif number == step %>
|
|
39
|
+
<%# Current: outlined in primary, named by aria-current. %>
|
|
40
|
+
<li aria-current="step" class="flex items-center gap-3">
|
|
41
|
+
<span class="flex size-8 items-center justify-center rounded-full border-2 border-primary text-sm font-semibold text-primary"><%= number %></span>
|
|
42
|
+
<span class="text-sm font-medium"><%= label %></span>
|
|
43
|
+
</li>
|
|
44
|
+
<% else %>
|
|
45
|
+
<%# Upcoming: muted and inert until reached. %>
|
|
46
|
+
<li class="flex items-center gap-3">
|
|
47
|
+
<span class="flex size-8 items-center justify-center rounded-full border border-border text-sm text-muted-foreground"><%= number %></span>
|
|
48
|
+
<span class="text-sm text-muted-foreground"><%= label %></span>
|
|
49
|
+
</li>
|
|
50
|
+
<% end %>
|
|
51
|
+
<% end %>
|
|
52
|
+
</ol>
|
|
53
|
+
</nav>
|
|
54
|
+
|
|
55
|
+
<div class="mt-6">
|
|
56
|
+
<%= poetry_card do |card| %>
|
|
57
|
+
<% card.with_title { title } %>
|
|
58
|
+
<% card.with_description { description } %>
|
|
59
|
+
<div class="space-y-4 text-sm">
|
|
60
|
+
<p><%= body %></p>
|
|
61
|
+
</div>
|
|
62
|
+
<% card.with_footer do %>
|
|
63
|
+
<div class="flex w-full items-center justify-between">
|
|
64
|
+
<% if step > 1 %>
|
|
65
|
+
<%= poetry_button(tag: :a, href: "?step=#{step - 1}", variant: :outline) { "Back" } %>
|
|
66
|
+
<% else %>
|
|
67
|
+
<span></span>
|
|
68
|
+
<% end %>
|
|
69
|
+
<% if step < steps.length %>
|
|
70
|
+
<%= poetry_button(tag: :a, href: "?step=#{step + 1}") { "Continue to #{steps[step].downcase}" } %>
|
|
71
|
+
<% else %>
|
|
72
|
+
<%# The terminal action: wire this to your submit. %>
|
|
73
|
+
<%= poetry_button { "Place order" } %>
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<%# poetry:block title="Top nav" description="A site navigation bar: brand mark, a products dropdown of rich title-and-description links, direct destinations, and the log-in / sign-up action pair." keywords="navigation, navbar, topbar, site, marketing, links" %>
|
|
2
|
+
<header class="w-full border-b">
|
|
3
|
+
<div class="mx-auto flex h-14 max-w-5xl items-center justify-between gap-6 px-6">
|
|
4
|
+
<%= poetry_link(href: "/", underline: :none, class: "inline-flex items-center gap-2 text-sm font-semibold") do %>
|
|
5
|
+
<%= poetry_icon(name: :"audio-waveform", class: "size-4") %> Meridian
|
|
6
|
+
<% end %>
|
|
7
|
+
<%= poetry_navigation_menu(label: "Main", viewport: true) do |nav| %>
|
|
8
|
+
<% nav.with_item("Products", value: "products") do %>
|
|
9
|
+
<div class="grid w-96 grid-cols-2 gap-1 p-2">
|
|
10
|
+
<%= poetry_navigation_menu_link(href: "/products/orders") do %>
|
|
11
|
+
<div class="space-y-0.5">
|
|
12
|
+
<div class="text-sm font-medium">Orders</div>
|
|
13
|
+
<p class="text-xs text-muted-foreground">Fulfillment from checkout to doorstep.</p>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
<%= poetry_navigation_menu_link(href: "/products/customers") do %>
|
|
17
|
+
<div class="space-y-0.5">
|
|
18
|
+
<div class="text-sm font-medium">Customers</div>
|
|
19
|
+
<p class="text-xs text-muted-foreground">Profiles, segments, and lifetime history.</p>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
<%= poetry_navigation_menu_link(href: "/products/analytics") do %>
|
|
23
|
+
<div class="space-y-0.5">
|
|
24
|
+
<div class="text-sm font-medium">Analytics</div>
|
|
25
|
+
<p class="text-xs text-muted-foreground">Live dashboards over every workspace event.</p>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
<%= poetry_navigation_menu_link(href: "/products/automations") do %>
|
|
29
|
+
<div class="space-y-0.5">
|
|
30
|
+
<div class="text-sm font-medium">Automations</div>
|
|
31
|
+
<p class="text-xs text-muted-foreground">Workflows that run while the team sleeps.</p>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
<% nav.with_link("Pricing", href: "/pricing") %>
|
|
37
|
+
<% nav.with_link("Docs", href: "/docs") %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<div class="flex items-center gap-2">
|
|
40
|
+
<%= poetry_button(variant: :ghost, size: :sm, tag: :a, href: "/login") { "Log in" } %>
|
|
41
|
+
<%= poetry_button(size: :sm, tag: :a, href: "/signup") { "Sign up" } %>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</header>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Poetry
|
|
7
|
+
# `rails g poetry:diff` - the copy-in upgrade report. poetry's
|
|
8
|
+
# three ownership tiers upgrade differently: gem-owned code rides
|
|
9
|
+
# `bundle update`, the vendored css set rides a `poetry:install` re-run,
|
|
10
|
+
# and copy-ins are app-OWNED - nothing may rewrite them. This generator
|
|
11
|
+
# closes the loop on that third tier: it reads the provenance
|
|
12
|
+
# manifest (config/poetry_components.yml) and reports, file by file, where
|
|
13
|
+
# the app's copies stand against what the installed gems ship NOW.
|
|
14
|
+
# Read-only by contract - it prints, it never writes; `poetry:add`
|
|
15
|
+
# re-run is the (skip-if-exists) way to pick up newly-shipped files.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# bin/rails g poetry:diff
|
|
19
|
+
class DiffGenerator < Rails::Generators::Base
|
|
20
|
+
# The copy-in provenance manifest poetry:add records into.
|
|
21
|
+
MANIFEST = "config/poetry_components.yml"
|
|
22
|
+
# Where component copy-ins land in the host app.
|
|
23
|
+
COMPONENT_ROOT = "app/components/poetry/ui"
|
|
24
|
+
# Where block copy-ins land in the host app.
|
|
25
|
+
BLOCKS_ROOT = "app/views/blocks"
|
|
26
|
+
# The ownership header poetry:block stamps on copy (stripped before
|
|
27
|
+
# comparing, like the gem template's own poetry:block header).
|
|
28
|
+
BLOCK_HEADER = /\A<%#[^%]*%>\n?/
|
|
29
|
+
|
|
30
|
+
desc "Report drift between copied-in poetry components/blocks and what the installed gems ship (read-only)"
|
|
31
|
+
|
|
32
|
+
# Step: reports drift for every manifest-recorded component copy-in.
|
|
33
|
+
# @api private
|
|
34
|
+
def report_components
|
|
35
|
+
entries = manifest_components
|
|
36
|
+
if entries.empty?
|
|
37
|
+
say "no copy-ins recorded in #{MANIFEST} - nothing to diff"
|
|
38
|
+
else
|
|
39
|
+
entries.each { |name, record| report_component(name, record) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Step: reports how copied blocks differ from the gem templates.
|
|
44
|
+
# @api private
|
|
45
|
+
def report_blocks
|
|
46
|
+
copied = Dir[File.join(destination_root, BLOCKS_ROOT, "_*.html.erb")]
|
|
47
|
+
return if copied.empty?
|
|
48
|
+
|
|
49
|
+
say "blocks are app-owned starting points - differing from the gem is normal, not drift:"
|
|
50
|
+
copied.each { |path| report_block(path) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def report_component(name, record)
|
|
56
|
+
return report_remote(name, record) if record.is_a?(Hash) && record.key?("source")
|
|
57
|
+
|
|
58
|
+
version = record.is_a?(Hash) ? record["version"] : nil
|
|
59
|
+
gem_dir = Poetry::Ui.root.join("app/components/poetry/ui", name)
|
|
60
|
+
unless gem_dir.directory?
|
|
61
|
+
say_status :gone, "#{name} - copied at #{version}, no longer shipped by poetry-ui", :yellow
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
changes = component_changes(name, gem_dir)
|
|
66
|
+
header = "#{name} - copied at #{version || "unknown"}, gem ships #{Poetry::Ui::VERSION}"
|
|
67
|
+
if changes.empty?
|
|
68
|
+
say_status :same, "#{header}: all files match", :green
|
|
69
|
+
else
|
|
70
|
+
say_status :drift, header, :yellow
|
|
71
|
+
changes.each { |line| say " #{line}" }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# One line per file that is not byte-identical, in gem order, then any
|
|
76
|
+
# app-local extras. Line counts, not hunks - the report is a pointer,
|
|
77
|
+
# the diff tool of record is git in the app repo.
|
|
78
|
+
def component_changes(name, gem_dir)
|
|
79
|
+
changes = []
|
|
80
|
+
gem_files = gem_dir.glob("**/*").select(&:file?).sort
|
|
81
|
+
gem_files.each do |file|
|
|
82
|
+
relative = file.relative_path_from(gem_dir)
|
|
83
|
+
app_path = File.join(destination_root, COMPONENT_ROOT, name, relative.to_s)
|
|
84
|
+
if !File.exist?(app_path)
|
|
85
|
+
changes << "#{relative}: not copied locally (new upstream file? `bin/rails g poetry:add #{name}` " \
|
|
86
|
+
"adds missing files without touching existing ones)"
|
|
87
|
+
elsif File.read(app_path) != file.read
|
|
88
|
+
changes << "#{relative}: differs (app #{File.foreach(app_path).count} lines, " \
|
|
89
|
+
"gem #{file.each_line.count} lines)"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
app_dir = File.join(destination_root, COMPONENT_ROOT, name)
|
|
93
|
+
Dir[File.join(app_dir, "**/*")].select { |f| File.file?(f) }.sort.each do |path|
|
|
94
|
+
relative = path.delete_prefix("#{app_dir}/")
|
|
95
|
+
changes << "#{relative}: local-only (yours)" unless gem_dir.join(relative).file?
|
|
96
|
+
end
|
|
97
|
+
changes
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def report_remote(name, record)
|
|
101
|
+
say_status :remote, "#{name} - installed from #{record["source"]}; re-run " \
|
|
102
|
+
"`bin/rails g poetry:add #{record["source"]}` to compare (skip-if-exists)", :cyan
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def report_block(path)
|
|
106
|
+
name = File.basename(path, ".html.erb").delete_prefix("_").tr("_", "-")
|
|
107
|
+
entry = block_catalog[name]
|
|
108
|
+
unless entry
|
|
109
|
+
say_status :yours, "#{name} - not in the gem catalog (renamed or your own)", :cyan
|
|
110
|
+
return
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
gem_body = Poetry::Ui.root.join(entry.fetch("template")).read.sub(BLOCK_HEADER, "")
|
|
114
|
+
app_body = File.read(path).sub(BLOCK_HEADER, "")
|
|
115
|
+
if app_body == gem_body
|
|
116
|
+
say_status :same, "#{name} - matches the gem's current template", :green
|
|
117
|
+
else
|
|
118
|
+
say_status :edited, "#{name} - differs from the gem's current template " \
|
|
119
|
+
"(reference: #{entry.fetch("template")})", :cyan
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def manifest_components
|
|
124
|
+
path = File.join(destination_root, MANIFEST)
|
|
125
|
+
config = File.exist?(path) ? YAML.safe_load_file(path) : nil
|
|
126
|
+
config.is_a?(Hash) && config["components"].is_a?(Hash) ? config["components"] : {}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def block_catalog
|
|
130
|
+
@block_catalog ||= YAML.safe_load_file(
|
|
131
|
+
Poetry::Ui.root.join(Poetry::Core::Registry::RELATIVE_PATH)
|
|
132
|
+
)["blocks"] || {}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "json"
|
|
5
|
+
require "yaml"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
module Poetry
|
|
9
|
+
# `rails g poetry:editor` - wire the poetry agent surface into the editors a
|
|
10
|
+
# Rails team actually uses. poetry has no bespoke extension (a possible
|
|
11
|
+
# future direction); what it DOES have is a standard MCP stdio server and a
|
|
12
|
+
# source-generated component registry, so this generator emits:
|
|
13
|
+
#
|
|
14
|
+
# .mcp.json Claude Code (mcpServers)
|
|
15
|
+
# .cursor/mcp.json Cursor (mcpServers)
|
|
16
|
+
# .vscode/mcp.json VS Code (servers + type: stdio)
|
|
17
|
+
# .vscode/poetry.code-snippets one snippet per poetry_* helper, with the
|
|
18
|
+
# real variant/size enums as tab-stop choices
|
|
19
|
+
# .herb.yml Herb (linter / formatter / language server), unless
|
|
20
|
+
# the app already has one
|
|
21
|
+
# .stimulus-lsp/config.json poetry's controller identifiers for Stimulus LSP
|
|
22
|
+
# (upsert: an existing ignore list keeps its entries)
|
|
23
|
+
#
|
|
24
|
+
# The MCP writes are upserts: an existing config keeps its other servers and
|
|
25
|
+
# gains a `poetry` entry; a config that already has one is left untouched; a
|
|
26
|
+
# JSONC file poetry can't parse is reported, never clobbered. Editors whose
|
|
27
|
+
# MCP config is global / IDE-managed (Zed, Windsurf, RubyMine) get a
|
|
28
|
+
# copy-paste block printed instead.
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# bin/rails g poetry:editor
|
|
32
|
+
class EditorGenerator < Rails::Generators::Base
|
|
33
|
+
source_root File.expand_path("templates", __dir__)
|
|
34
|
+
|
|
35
|
+
# Editors whose MCP config is global or IDE-managed, so poetry prints a
|
|
36
|
+
# paste-block instead of writing a project file.
|
|
37
|
+
MANUAL_EDITORS = <<~TEXT
|
|
38
|
+
Zed settings.json ->
|
|
39
|
+
"context_servers": { "poetry": { "command": { "path": "bundle", "args": ["exec", "poetry-agent"] } } }
|
|
40
|
+
Windsurf ~/.codeium/windsurf/mcp_config.json ->
|
|
41
|
+
{ "mcpServers": { "poetry": { "command": "bundle", "args": ["exec", "poetry-agent"] } } }
|
|
42
|
+
RubyMine Settings -> Tools -> AI Assistant -> Model Context Protocol (MCP) ->
|
|
43
|
+
add a stdio server: command `bundle exec poetry-agent` (or import .mcp.json). RubyMine 2025.2+.
|
|
44
|
+
TEXT
|
|
45
|
+
|
|
46
|
+
# The Herb toolchain (HTML+ERB parser, linter, formatter, language server)
|
|
47
|
+
# reads one file. Poetry templates are gated to parse AND compile under
|
|
48
|
+
# Herb (Rails core's ERB engine since 8.2), so a Poetry app can run the
|
|
49
|
+
# whole toolchain; two linter rules misread ViewComponent slot setters on
|
|
50
|
+
# helper-yielded builders today, so they start off with the upstream
|
|
51
|
+
# issues to watch. The version pin keeps a linter upgrade from enabling
|
|
52
|
+
# rules silently.
|
|
53
|
+
HERB_CONFIG = <<~YML
|
|
54
|
+
# Herb toolchain configuration (linter / formatter / language server).
|
|
55
|
+
# https://herb-tools.dev/configuration - written by `rails g poetry:editor`.
|
|
56
|
+
version: 0.10.3
|
|
57
|
+
|
|
58
|
+
framework: actionview
|
|
59
|
+
|
|
60
|
+
linter:
|
|
61
|
+
enabled: true
|
|
62
|
+
rules:
|
|
63
|
+
# `<%= poetry_card do |card| %><% card.with_title(...) %>` is flagged as
|
|
64
|
+
# a discarded value until marcoroth/herb#2426 lands.
|
|
65
|
+
erb-no-unused-expressions:
|
|
66
|
+
enabled: false
|
|
67
|
+
# `<% item.with_icon { ... } %>` (brace-form slot setters) is flagged and
|
|
68
|
+
# the autofix renders the wrong thing - marcoroth/herb#2340.
|
|
69
|
+
actionview-no-silent-helper:
|
|
70
|
+
enabled: false
|
|
71
|
+
|
|
72
|
+
formatter:
|
|
73
|
+
enabled: false
|
|
74
|
+
YML
|
|
75
|
+
|
|
76
|
+
# Where the Herb language server comes from, per editor.
|
|
77
|
+
HERB_EDITORS = <<~TEXT
|
|
78
|
+
VS Code install the "Herb LSP" extension (marcoroth.herb-lsp); Stimulus LSP (marcoroth.stimulus-lsp)
|
|
79
|
+
understands the same HTML+ERB
|
|
80
|
+
Zed ships inside the official Ruby extension - nothing to add
|
|
81
|
+
Neovim npm install -g @herb-tools/language-server, then lspconfig's herb_ls
|
|
82
|
+
CI npx --yes @herb-tools/linter app (or bundle exec herb lint)
|
|
83
|
+
Stimulus .stimulus-lsp/config.json lists poetry's controllers for Stimulus LSP - it checks YOUR
|
|
84
|
+
controllers; bin/rails poetry:check validates poetry's (data: keywords included)
|
|
85
|
+
TEXT
|
|
86
|
+
|
|
87
|
+
# Stimulus LSP resolves identifiers from the app's controller directories
|
|
88
|
+
# and node_modules; controllers a gem serves through importmap are
|
|
89
|
+
# invisible to it, so every hand-written `poetry--...` descriptor reads
|
|
90
|
+
# as an invalid controller with a quick-fix that scaffolds a bogus file.
|
|
91
|
+
# The LSP's one lever is an ignore list of exact identifiers, so poetry
|
|
92
|
+
# lists every controller its installed gems ship. Those descriptors are
|
|
93
|
+
# poetry:check's to validate (kwargs included); the LSP keeps yours.
|
|
94
|
+
STIMULUS_LSP_VERSION = "1.1.2" # the schema's writer version; the LSP rewrites it on its own saves
|
|
95
|
+
|
|
96
|
+
# The check hook: stdlib Ruby that runs `poetry check` on the one app
|
|
97
|
+
# template an agent just edited and feeds the findings back (errors as a
|
|
98
|
+
# blocking report, warnings as context). Registered for Claude Code and
|
|
99
|
+
# Cursor; the script filters to app templates itself, since neither
|
|
100
|
+
# hook's matcher can see a file path.
|
|
101
|
+
HOOK_SCRIPT = "bin/poetry-check-hook"
|
|
102
|
+
# The Claude Code entry: PostToolUse on the file-editing tools.
|
|
103
|
+
CLAUDE_HOOK = { "matcher" => "Edit|Write|MultiEdit",
|
|
104
|
+
"hooks" => [{ "type" => "command", "command" => HOOK_SCRIPT, "timeout" => 120 }] }.freeze
|
|
105
|
+
# Cursor's Write is its file-edit tool type; the payload is read
|
|
106
|
+
# leniently and the hook stays silent when it finds no path.
|
|
107
|
+
CURSOR_HOOK = { "command" => HOOK_SCRIPT, "matcher" => "Write", "timeout" => 120 }.freeze
|
|
108
|
+
# What the generator says about the hook once it is written.
|
|
109
|
+
HOOK_NOTE = <<~TEXT
|
|
110
|
+
bin/poetry-check-hook runs poetry check on every app template an agent edits and feeds the
|
|
111
|
+
findings back: registered in .claude/settings.json (Claude Code, PostToolUse on Edit/Write) and
|
|
112
|
+
.cursor/hooks.json (Cursor, postToolUse on Write). Claude Code reads hooks at session start -
|
|
113
|
+
start a new session (or check /hooks) before expecting feedback.
|
|
114
|
+
TEXT
|
|
115
|
+
|
|
116
|
+
desc "Wire poetry's MCP server + component snippets + Herb and Stimulus LSP config + the check hook into " \
|
|
117
|
+
"your editors (.mcp.json / .cursor/mcp.json / .vscode/mcp.json + .vscode/poetry.code-snippets + " \
|
|
118
|
+
".herb.yml + .stimulus-lsp/config.json + bin/poetry-check-hook wired into .claude/settings.json and " \
|
|
119
|
+
".cursor/hooks.json)"
|
|
120
|
+
|
|
121
|
+
# Step: upserts the poetry server into .mcp.json.
|
|
122
|
+
# @api private
|
|
123
|
+
def write_claude_code_config
|
|
124
|
+
upsert_mcp ".mcp.json", "mcpServers", stdio: false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Step: upserts the poetry server into .cursor/mcp.json.
|
|
128
|
+
# @api private
|
|
129
|
+
def write_cursor_config
|
|
130
|
+
upsert_mcp ".cursor/mcp.json", "mcpServers", stdio: false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Step: upserts the poetry server into .vscode/mcp.json.
|
|
134
|
+
# @api private
|
|
135
|
+
def write_vscode_config
|
|
136
|
+
upsert_mcp ".vscode/mcp.json", "servers", stdio: true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Step: writes one VS Code snippet per poetry_* helper.
|
|
140
|
+
# @api private
|
|
141
|
+
def write_snippets
|
|
142
|
+
create_file ".vscode/poetry.code-snippets", "#{JSON.pretty_generate(snippets)}\n", force: true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Step: writes .herb.yml for the Herb toolchain, unless the app has one.
|
|
146
|
+
# @api private
|
|
147
|
+
def write_herb_config
|
|
148
|
+
if File.exist?(File.join(destination_root, ".herb.yml"))
|
|
149
|
+
say_status :skip, ".herb.yml exists - keeping yours (poetry's rule notes: /editors on the docs site)",
|
|
150
|
+
:yellow
|
|
151
|
+
else
|
|
152
|
+
create_file ".herb.yml", HERB_CONFIG
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Step: lists poetry's controller identifiers for Stimulus LSP (upsert).
|
|
157
|
+
# @api private
|
|
158
|
+
def write_stimulus_lsp_config
|
|
159
|
+
upsert_stimulus_lsp ".stimulus-lsp/config.json"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Step: writes the check hook script.
|
|
163
|
+
# @api private
|
|
164
|
+
def write_check_hook
|
|
165
|
+
copy_file "poetry-check-hook", HOOK_SCRIPT
|
|
166
|
+
chmod HOOK_SCRIPT, 0o755
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Step: registers the hook for Claude Code (upsert into .claude/settings.json).
|
|
170
|
+
# @api private
|
|
171
|
+
def write_claude_code_hook
|
|
172
|
+
upsert_hook ".claude/settings.json", "PostToolUse", CLAUDE_HOOK
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Step: registers the hook for Cursor (upsert into .cursor/hooks.json).
|
|
176
|
+
# @api private
|
|
177
|
+
def write_cursor_hook
|
|
178
|
+
upsert_hook ".cursor/hooks.json", "postToolUse", CURSOR_HOOK, seed: { "version" => 1 }
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Step: explains the check hook and when each editor picks it up.
|
|
182
|
+
# @api private
|
|
183
|
+
def announce_check_hook
|
|
184
|
+
say "\npoetry:editor - the check hook:", :green
|
|
185
|
+
say HOOK_NOTE
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Step: prints the paste-blocks for IDE-managed MCP configs.
|
|
189
|
+
# @api private
|
|
190
|
+
def announce_manual_editors
|
|
191
|
+
say "\npoetry:editor - editors with global / IDE-managed MCP config (paste-blocks):", :green
|
|
192
|
+
say MANUAL_EDITORS
|
|
193
|
+
say "\npoetry:editor - Herb (HTML+ERB linter + language server), configured by .herb.yml:", :green
|
|
194
|
+
say HERB_EDITORS
|
|
195
|
+
say "Full setup + a per-editor matrix: /editors on the docs site.\n"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
# The stdio server every host launches the same way. Zero config beyond the
|
|
201
|
+
# bundle exec line; the agent auto-locates the gem and reads the committed
|
|
202
|
+
# registry (boot-free).
|
|
203
|
+
def server_entry(stdio:)
|
|
204
|
+
entry = { "command" => "bundle", "args" => %w[exec poetry-agent] }
|
|
205
|
+
stdio ? { "type" => "stdio" }.merge(entry) : entry
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def upsert_mcp(relative, key, stdio:)
|
|
209
|
+
path = File.join(destination_root, relative)
|
|
210
|
+
unless File.exist?(path)
|
|
211
|
+
create_file relative, "#{JSON.pretty_generate(key => { "poetry" => server_entry(stdio: stdio) })}\n"
|
|
212
|
+
return
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
data = parse_json(File.read(path))
|
|
216
|
+
return say_status(:skip, "#{relative} isn't plain JSON (JSONC?) - add the poetry server by hand", :yellow) \
|
|
217
|
+
if data.nil?
|
|
218
|
+
|
|
219
|
+
servers = (data[key] ||= {})
|
|
220
|
+
return say_status(:identical, relative, :blue) if servers["poetry"]
|
|
221
|
+
|
|
222
|
+
servers["poetry"] = server_entry(stdio: stdio)
|
|
223
|
+
File.write(path, "#{JSON.pretty_generate(data)}\n")
|
|
224
|
+
say_status :update, "#{relative} (added the poetry server)", :green
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Upserts one hook entry under hooks.<event>: a file already naming the
|
|
228
|
+
# script anywhere in that list is identical, other hooks and keys are
|
|
229
|
+
# kept, a JSONC file is reported and never clobbered.
|
|
230
|
+
def upsert_hook(relative, event, entry, seed: {})
|
|
231
|
+
path = File.join(destination_root, relative)
|
|
232
|
+
unless File.exist?(path)
|
|
233
|
+
create_file relative, "#{JSON.pretty_generate(seed.merge("hooks" => { event => [entry] }))}\n"
|
|
234
|
+
return
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
data = parse_json(File.read(path))
|
|
238
|
+
if data.nil?
|
|
239
|
+
return say_status(:skip, "#{relative} isn't plain JSON (JSONC?) - add the poetry hook by hand",
|
|
240
|
+
:yellow)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
hooks = (data["hooks"] ||= {})
|
|
244
|
+
entries = hooks.is_a?(Hash) ? (hooks[event] ||= []) : nil
|
|
245
|
+
unless entries.is_a?(Array)
|
|
246
|
+
return say_status(:skip, "#{relative} has an unexpected hooks shape - add the poetry hook by hand", :yellow)
|
|
247
|
+
end
|
|
248
|
+
return say_status(:identical, relative, :blue) if JSON.generate(entries).include?(HOOK_SCRIPT)
|
|
249
|
+
|
|
250
|
+
entries << entry
|
|
251
|
+
File.write(path, "#{JSON.pretty_generate(data)}\n")
|
|
252
|
+
say_status :update, "#{relative} (added the poetry check hook)", :green
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Every poetry-owned identifier the installed gems ship (core + any
|
|
256
|
+
# registered manifest), sorted for a stable file.
|
|
257
|
+
def poetry_identifiers
|
|
258
|
+
Poetry::Core::Stimulus::Manifest.catalog.keys.sort
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def upsert_stimulus_lsp(relative)
|
|
262
|
+
path = File.join(destination_root, relative)
|
|
263
|
+
identifiers = poetry_identifiers
|
|
264
|
+
unless File.exist?(path)
|
|
265
|
+
create_file relative, "#{JSON.pretty_generate(stimulus_lsp_config(identifiers))}\n"
|
|
266
|
+
return
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
data = parse_json(File.read(path))
|
|
270
|
+
return say_status(:skip, "#{relative} isn't plain JSON - add poetry's identifiers by hand", :yellow) if data.nil?
|
|
271
|
+
|
|
272
|
+
options = (data["options"] ||= {})
|
|
273
|
+
ignored = Array(options["ignoredControllerIdentifiers"])
|
|
274
|
+
missing = identifiers - ignored
|
|
275
|
+
return say_status(:identical, relative, :blue) if missing.empty?
|
|
276
|
+
|
|
277
|
+
options["ignoredControllerIdentifiers"] = (ignored + missing).uniq.sort
|
|
278
|
+
options["ignoredAttributes"] = Array(options["ignoredAttributes"])
|
|
279
|
+
data["updatedAt"] = Time.now.utc.iso8601
|
|
280
|
+
File.write(path, "#{JSON.pretty_generate(data)}\n")
|
|
281
|
+
say_status :update, "#{relative} (+#{missing.size} poetry controller identifiers)", :green
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def stimulus_lsp_config(identifiers)
|
|
285
|
+
now = Time.now.utc.iso8601
|
|
286
|
+
{ "version" => STIMULUS_LSP_VERSION, "createdAt" => now, "updatedAt" => now,
|
|
287
|
+
"options" => { "ignoredControllerIdentifiers" => identifiers, "ignoredAttributes" => [] } }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# nil for anything we can't safely round-trip. JSONC comments are detected
|
|
291
|
+
# BEFORE parsing: some json versions silently accept `//`/`/* */` and would
|
|
292
|
+
# then drop the comments on re-serialize - poetry never clobbers a config it
|
|
293
|
+
# can't preserve.
|
|
294
|
+
def parse_json(text)
|
|
295
|
+
return nil if text.match?(%r{^\s*//}) || text.include?("/*")
|
|
296
|
+
|
|
297
|
+
JSON.parse(text)
|
|
298
|
+
rescue JSON::ParserError
|
|
299
|
+
nil
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# One snippet per main component helper (poetry_<name>), driven by the
|
|
303
|
+
# committed registry so it never drifts from the shipped catalog. A block
|
|
304
|
+
# form for components that compose via slots; inline for atoms. The primary
|
|
305
|
+
# enum (variant, else the first declared one) becomes a choice tab-stop, so
|
|
306
|
+
# the completion only offers values that actually exist.
|
|
307
|
+
def snippets
|
|
308
|
+
registry = YAML.safe_load_file(Poetry::Ui.root.join("config/component_registry.yml"))
|
|
309
|
+
registry.fetch("components").keys.sort.to_h do |path|
|
|
310
|
+
name = path.delete_prefix("poetry/ui/").tr("/", "_")
|
|
311
|
+
component = registry.fetch("components").fetch(path)
|
|
312
|
+
[
|
|
313
|
+
"poetry #{name.tr("_", " ")}",
|
|
314
|
+
{ "prefix" => "poetry_#{name}", "scope" => "erb",
|
|
315
|
+
"body" => snippet_body(name, component), "description" => "poetry #{humanize(name)} component" }
|
|
316
|
+
]
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def snippet_body(name, component)
|
|
321
|
+
call = "poetry_#{name}#{enum_argument(component)}"
|
|
322
|
+
if Array(component["slots"]).any?
|
|
323
|
+
["<%= #{call} do |c| %>", "\t$0", "<% end %>"]
|
|
324
|
+
else
|
|
325
|
+
"<%= #{call} %>$0"
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# `(variant: :${1|a,b,c|})` for the primary enum, or "" when the component
|
|
330
|
+
# declares none. Enums come from BOTH the styles DSL (variant/size) and
|
|
331
|
+
# options (tag/orientation/...); a declared `variant` wins.
|
|
332
|
+
def enum_argument(component)
|
|
333
|
+
enums = (Array(component["styles"]) + Array(component["options"])).select { |o| o["variants"] }
|
|
334
|
+
chosen = enums.find { |o| o["name"] == "variant" } || enums.first
|
|
335
|
+
return "" unless chosen
|
|
336
|
+
|
|
337
|
+
"(#{chosen["name"]}: :${1|#{Array(chosen["variants"]).join(",")}|})"
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def humanize(name)
|
|
341
|
+
name.split("_").map(&:capitalize).join(" ")
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|