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,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# File/image upload chips.
|
|
6
|
+
module Attachment
|
|
7
|
+
# A file or image chip showing an upload's name, metadata, and
|
|
8
|
+
# lifecycle state. The server owns the state: render state:
|
|
9
|
+
# (idle/uploading/processing/error/done) and update it by
|
|
10
|
+
# re-rendering or a Turbo Stream replace - the component ships no
|
|
11
|
+
# upload JavaScript of its own.
|
|
12
|
+
#
|
|
13
|
+
# In-flight and error states carry a translated screen-reader
|
|
14
|
+
# status announcement; the visual lifecycle is pure CSS.
|
|
15
|
+
#
|
|
16
|
+
# @example A finished upload
|
|
17
|
+
# render Poetry::Ui::Attachment::Component.new do |attachment|
|
|
18
|
+
# attachment.with_title { "quarterly-report.pdf" }
|
|
19
|
+
# attachment.with_description { "1.2 MB" }
|
|
20
|
+
# end
|
|
21
|
+
class Component < Poetry::Core::Component
|
|
22
|
+
# The closed vocabulary for the state axis (the server-owned upload lifecycle).
|
|
23
|
+
STATES = %i[idle uploading processing error done].freeze
|
|
24
|
+
# The closed vocabulary for the size axis.
|
|
25
|
+
SIZES = %i[default sm xs].freeze
|
|
26
|
+
# The closed vocabulary for the orientation axis.
|
|
27
|
+
ORIENTATIONS = %i[horizontal vertical].freeze
|
|
28
|
+
# The closed vocabulary for the with_media variant.
|
|
29
|
+
MEDIA_VARIANTS = %i[icon image].freeze
|
|
30
|
+
# States that render the screen-reader status announcement.
|
|
31
|
+
ANNOUNCED_STATES = %i[uploading processing error].freeze
|
|
32
|
+
|
|
33
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
34
|
+
AGENT_RULES = [
|
|
35
|
+
"State is server-owned: render data-upload-state and flip it by Turbo Stream replace - " \
|
|
36
|
+
"never toggle it in JS.",
|
|
37
|
+
"with_media(variant: :image) wraps the caller's <img>; file names and URLs are " \
|
|
38
|
+
"user content - never render them html_safe.",
|
|
39
|
+
"Actions are with_action(...) poetry Buttons (ghost/icon-xs defaults) - each needs label: (icon-only).",
|
|
40
|
+
"with_trigger makes the whole chip the control (a stretched overlay UNDER the " \
|
|
41
|
+
"actions) - don't also wrap the chip in a link.",
|
|
42
|
+
"error state needs a with_description explaining the failure - the tint alone is not the message."
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
45
|
+
renders_one :media,
|
|
46
|
+
doc: "Leading visual: :icon (default) boxes an icon tile, :image wraps the caller's <img>.",
|
|
47
|
+
renders: lambda { |variant: :icon, &block|
|
|
48
|
+
unless MEDIA_VARIANTS.include?(variant)
|
|
49
|
+
raise ArgumentError,
|
|
50
|
+
"media variant must be :icon or :image"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
content_tag(:div, "data-slot" => "attachment-media", "data-variant" => variant,
|
|
54
|
+
class: css(:media, class: (if variant == :image
|
|
55
|
+
"cn-attachment-media-variant-image"
|
|
56
|
+
end)), &block)
|
|
57
|
+
}
|
|
58
|
+
renders_one :title, doc: "The file name line. User content - never mark it html_safe."
|
|
59
|
+
renders_one :description, doc: "Muted metadata under the title (size, type); in the error state, the " \
|
|
60
|
+
"failure explanation."
|
|
61
|
+
renders_many :actions,
|
|
62
|
+
doc: "Trailing icon actions - each renders a Button (ghost, icon-xs defaults) and requires " \
|
|
63
|
+
"label:.",
|
|
64
|
+
renders: lambda { |label:, **options, &block|
|
|
65
|
+
# Caller data: augments the slot marker instead of replacing it
|
|
66
|
+
# at the kwargs splat.
|
|
67
|
+
data = { slot: "attachment-action" }.merge(options.delete(:data) || {})
|
|
68
|
+
Button::Component.new(variant: options.delete(:variant) || :ghost,
|
|
69
|
+
size: options.delete(:size) || :"icon-xs",
|
|
70
|
+
label: label, data: data, **options, &block)
|
|
71
|
+
}
|
|
72
|
+
renders_one :trigger,
|
|
73
|
+
doc: "Makes the whole chip the control - a stretched button (or anchor via tag: :a, href:) " \
|
|
74
|
+
"layered under the actions. Don't also wrap the chip in a link.",
|
|
75
|
+
renders: lambda { |tag: :button, href: nil, **options, &block|
|
|
76
|
+
attrs = Poetry::Core::HTML::Attributes.merged(
|
|
77
|
+
{ class: css(:trigger), "data-slot" => "attachment-trigger" }, options
|
|
78
|
+
)
|
|
79
|
+
attrs[:type] = "button" if tag == :button
|
|
80
|
+
attrs[:href] = href if tag == :a
|
|
81
|
+
content_tag(tag, attrs, &block)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
style :size, default: :default, required: true, variants: SIZES, doc: "The chip density axis."
|
|
85
|
+
style :orientation, default: :horizontal, required: true, variants: ORIENTATIONS,
|
|
86
|
+
doc: "Row (:horizontal) or stacked thumbnail-card (:vertical) layout."
|
|
87
|
+
|
|
88
|
+
option :state, :symbol, default: :done,
|
|
89
|
+
doc: "The upload lifecycle state; flip it by re-render or Turbo Stream replace, " \
|
|
90
|
+
"never in JS."
|
|
91
|
+
|
|
92
|
+
validates :state, inclusion: { in: STATES }
|
|
93
|
+
|
|
94
|
+
part "attachment", "The chip root - the server-owned upload lifecycle rides here " \
|
|
95
|
+
"(flip data-upload-state by re-render / Turbo Stream replace)",
|
|
96
|
+
states: {
|
|
97
|
+
"data-upload-state" => { condition: "always - the resolved state",
|
|
98
|
+
values: STATES.map(&:to_s) },
|
|
99
|
+
"data-size" => { condition: "always - the resolved size",
|
|
100
|
+
values: SIZES.map(&:to_s) },
|
|
101
|
+
"data-orientation" => { condition: "always - the resolved orientation",
|
|
102
|
+
values: ORIENTATIONS.map(&:to_s) }
|
|
103
|
+
}
|
|
104
|
+
part "attachment-media", "The media slot's box - the icon tile or the caller's <img>",
|
|
105
|
+
states: {
|
|
106
|
+
"data-variant" => { condition: "always - the media variant",
|
|
107
|
+
values: MEDIA_VARIANTS.map(&:to_s) }
|
|
108
|
+
}
|
|
109
|
+
part "attachment-content", "Text column wrapping title/description - renders when " \
|
|
110
|
+
"either slot is set"
|
|
111
|
+
part "attachment-title", "The file name line (title slot; user content, never html_safe)"
|
|
112
|
+
part "attachment-description", "Muted metadata / failure copy under the title"
|
|
113
|
+
part "attachment-actions", "Row of with_action poetry Buttons"
|
|
114
|
+
part "attachment-trigger", "The whole-chip control (with_trigger: a button or tag: :a " \
|
|
115
|
+
"anchor) - wraps the picker/download affordance"
|
|
116
|
+
part "attachment-status", "sr-only role=status announcement for the in-flight and " \
|
|
117
|
+
"error states (uploading/processing/error)"
|
|
118
|
+
|
|
119
|
+
# Whether the current state renders the screen-reader status announcement.
|
|
120
|
+
# @api private
|
|
121
|
+
def announced?
|
|
122
|
+
ANNOUNCED_STATES.include?(state)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @api private
|
|
126
|
+
def root_attributes
|
|
127
|
+
html_attributes.merge_if_not_set(
|
|
128
|
+
{
|
|
129
|
+
"data-slot" => "attachment", "data-upload-state" => state,
|
|
130
|
+
"data-size" => size, "data-orientation" => orientation
|
|
131
|
+
}.merge(component_data_attributes)
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private :announced?, :root_attributes
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module Attachment
|
|
6
|
+
# The Attachment preview matrix - every lifecycle state + both
|
|
7
|
+
# orientations (the visual states are pure CSS on data-upload-state).
|
|
8
|
+
class Preview < Poetry::Core::Preview::Base
|
|
9
|
+
def default
|
|
10
|
+
render_component do |attachment|
|
|
11
|
+
attachment.with_media { icon(:file) }
|
|
12
|
+
attachment.with_title { "quarterly-report.pdf" }
|
|
13
|
+
attachment.with_description { "1.2 MB" }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The whole-chip trigger control (download/open affordance).
|
|
18
|
+
def with_trigger
|
|
19
|
+
render_component do |attachment|
|
|
20
|
+
attachment.with_trigger(tag: :a, href: "/files/report.pdf") { "Download" }
|
|
21
|
+
attachment.with_media { icon(:file) }
|
|
22
|
+
attachment.with_title { "quarterly-report.pdf" }
|
|
23
|
+
attachment.with_description { "1.2 MB" }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def uploading
|
|
28
|
+
render_component(state: :uploading) do |attachment|
|
|
29
|
+
attachment.with_media { icon(:file) }
|
|
30
|
+
attachment.with_title { "photo.png" }
|
|
31
|
+
attachment.with_description { "uploading…" }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def error
|
|
36
|
+
render_component(state: :error) do |attachment|
|
|
37
|
+
attachment.with_media { icon(:"circle-alert") }
|
|
38
|
+
attachment.with_title { "notes.txt" }
|
|
39
|
+
attachment.with_description { "Too large (max 10 MB)" }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def idle_dropzone
|
|
44
|
+
render_component(state: :idle) do |attachment|
|
|
45
|
+
attachment.with_media { icon(:plus) }
|
|
46
|
+
attachment.with_title { "Add a file" }
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The compact rows cover the size axis: sm for dense lists,
|
|
51
|
+
# xs for inline chips.
|
|
52
|
+
def size_sm
|
|
53
|
+
render_component(size: :sm) do |attachment|
|
|
54
|
+
attachment.with_media { icon(:file) }
|
|
55
|
+
attachment.with_title { "invoice.pdf" }
|
|
56
|
+
attachment.with_description { "84 KB" }
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def size_xs
|
|
61
|
+
render_component(size: :xs) do |attachment|
|
|
62
|
+
attachment.with_media { icon(:file) }
|
|
63
|
+
attachment.with_title { "readme.md" }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def vertical_with_actions
|
|
68
|
+
render_component(orientation: :vertical) do |attachment|
|
|
69
|
+
attachment.with_media(variant: :image) { icon(:image) }
|
|
70
|
+
attachment.with_title { "screenshot.png" }
|
|
71
|
+
attachment.with_action(label: "Remove") { icon(:x) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def icon(name)
|
|
78
|
+
embed(Icon::Component.new(name: name))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module Attachment
|
|
6
|
+
# Re-expressed through the cn-* theme layer. The upload
|
|
7
|
+
# lifecycle stays PURE CSS and INLINE per upstream's own split (idle
|
|
8
|
+
# dashes, error tints, the in-flight image dim) - it is coupled to
|
|
9
|
+
# the data-[upload-state] machine; sizes/orientation/surface ride the
|
|
10
|
+
# theme. w-fit moved theme-side WITH the vertical w-24 (split-side).
|
|
11
|
+
class Style < Poetry::Core::Style
|
|
12
|
+
base "cn-attachment group/attachment relative flex max-w-full min-w-0 shrink-0 flex-wrap border " \
|
|
13
|
+
"bg-card text-card-foreground transition-colors " \
|
|
14
|
+
"has-[>a,>button]:hover:bg-muted/50 data-[upload-state=error]:border-destructive/30 " \
|
|
15
|
+
"data-[upload-state=idle]:border-dashed"
|
|
16
|
+
|
|
17
|
+
variant :size, {
|
|
18
|
+
default: "cn-attachment-size-default",
|
|
19
|
+
sm: "cn-attachment-size-sm",
|
|
20
|
+
xs: "cn-attachment-size-xs"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
variant :orientation, {
|
|
24
|
+
horizontal: "cn-attachment-orientation-horizontal items-center",
|
|
25
|
+
vertical: "cn-attachment-orientation-vertical flex-col"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
element :media, "cn-attachment-media relative flex aspect-square shrink-0 items-center " \
|
|
29
|
+
"justify-center overflow-hidden " \
|
|
30
|
+
"group-data-[upload-state=error]/attachment:bg-destructive/10 " \
|
|
31
|
+
"group-data-[upload-state=error]/attachment:text-destructive " \
|
|
32
|
+
"group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! " \
|
|
33
|
+
"[&_svg]:pointer-events-none " \
|
|
34
|
+
"data-[variant=image]:opacity-60 " \
|
|
35
|
+
"data-[variant=image]:group-data-[upload-state=done]/attachment:opacity-100 " \
|
|
36
|
+
"data-[variant=image]:group-data-[upload-state=idle]/attachment:opacity-100 " \
|
|
37
|
+
"data-[variant=image]:*:[img]:aspect-square data-[variant=image]:*:[img]:w-full " \
|
|
38
|
+
"data-[variant=image]:*:[img]:object-cover"
|
|
39
|
+
|
|
40
|
+
element :content, "cn-attachment-content max-w-full min-w-0 flex-1"
|
|
41
|
+
|
|
42
|
+
element :title, "cn-attachment-title block max-w-full min-w-0 truncate " \
|
|
43
|
+
"group-data-[upload-state=processing]/attachment:shimmer " \
|
|
44
|
+
"group-data-[upload-state=uploading]/attachment:shimmer"
|
|
45
|
+
|
|
46
|
+
element :description, "cn-attachment-description block max-w-full min-w-0 truncate " \
|
|
47
|
+
"group-data-[upload-state=error]/attachment:text-destructive/80 text-muted-foreground"
|
|
48
|
+
|
|
49
|
+
element :actions, "cn-attachment-actions flex shrink-0 items-center"
|
|
50
|
+
|
|
51
|
+
element :trigger, "cn-attachment-trigger"
|
|
52
|
+
|
|
53
|
+
element :group, "cn-attachment-group flex min-w-0 scroll-fade-x snap-x snap-mandatory " \
|
|
54
|
+
"scrollbar-none overflow-x-auto overscroll-x-contain " \
|
|
55
|
+
"*:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= content_tag(:div, **root_attributes.to_attributes) do %>
|
|
2
|
+
<%= tag.input(**input_attributes, class: Poetry::Ui::Input::Style.css) %>
|
|
3
|
+
<%= tag.div(**content_attributes) do %>
|
|
4
|
+
<div data-slot="autocomplete-list" role="listbox" id="<%= list_id %>"
|
|
5
|
+
class="<%= Poetry::Ui::Command::Style.css(:list) %>"
|
|
6
|
+
<%= tag.attributes(stimulus_attributes_for(:list)) %>>
|
|
7
|
+
<% item_models.each_with_index do |item, index| %>
|
|
8
|
+
<%= tag.div(**item_attributes(item, index)) do %>
|
|
9
|
+
<span><%= item.label %></span>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% end %>
|
|
12
|
+
</div>
|
|
13
|
+
<div data-slot="autocomplete-empty" class="<%= Poetry::Ui::Command::Style.css(:empty) %>" hidden
|
|
14
|
+
<%= tag.attributes(stimulus_attributes_for(:empty)) %>><%= empty_text %></div>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% end %>
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# Free-text inputs with filtering suggestion popups.
|
|
6
|
+
module Autocomplete
|
|
7
|
+
# A text input that suggests from a server-rendered list, filtered
|
|
8
|
+
# as you type, while the text itself stays the form value: free text
|
|
9
|
+
# submits as an ordinary param, and picking a suggestion writes it
|
|
10
|
+
# into the input and closes the popup. When the value must be a
|
|
11
|
+
# selected item rather than free text, use Combobox instead.
|
|
12
|
+
#
|
|
13
|
+
# @example Free text with suggestions
|
|
14
|
+
# render Poetry::Ui::Autocomplete::Component.new(name: "tag", label: "Search tags") do |auto|
|
|
15
|
+
# auto.with_item(label: "feature")
|
|
16
|
+
# auto.with_item(label: "fix")
|
|
17
|
+
# end
|
|
18
|
+
class Component < Poetry::Core::Component
|
|
19
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
20
|
+
AGENT_RULES = [
|
|
21
|
+
"The input IS the value: name: is the param key and free text submits as-is - " \
|
|
22
|
+
"suggestions are conveniences, not constraints (constrained pick = Combobox).",
|
|
23
|
+
"Items via with_item(label:) - label is what filtering matches and what commit " \
|
|
24
|
+
"writes; value: overrides the committed text when it differs from the label.",
|
|
25
|
+
"empty_text: renders the no-matches state (hidden while anything matches).",
|
|
26
|
+
"open_on_focus: false waits for typing before suggesting.",
|
|
27
|
+
"Server-side filtering stays yours: render fewer items on re-render - the client " \
|
|
28
|
+
"filter only narrows what the server sent."
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
use_stimulus do
|
|
32
|
+
on :root do
|
|
33
|
+
controller :autocomplete do
|
|
34
|
+
register
|
|
35
|
+
value :open_on_focus, "false", if: -> { !open_on_focus }
|
|
36
|
+
end
|
|
37
|
+
controller(:popper) { register }
|
|
38
|
+
end
|
|
39
|
+
on :input do
|
|
40
|
+
controller :autocomplete do
|
|
41
|
+
target :input
|
|
42
|
+
action :input, on: :input
|
|
43
|
+
action :focus, on: :focus
|
|
44
|
+
action :blurred, on: :focusout
|
|
45
|
+
action :keydown, on: :keydown
|
|
46
|
+
end
|
|
47
|
+
controller(:popper) { target :anchor }
|
|
48
|
+
end
|
|
49
|
+
on :content do
|
|
50
|
+
controller(:autocomplete) { target :content }
|
|
51
|
+
controller(:popper) { target :content }
|
|
52
|
+
end
|
|
53
|
+
on :list do
|
|
54
|
+
controller(:autocomplete) { target :list }
|
|
55
|
+
end
|
|
56
|
+
on :empty do
|
|
57
|
+
controller(:autocomplete) { target :empty }
|
|
58
|
+
end
|
|
59
|
+
on :item do
|
|
60
|
+
controller :autocomplete do
|
|
61
|
+
action :itemPress, on: :pointerdown
|
|
62
|
+
action :itemEnter, on: :pointerenter
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
option :name, :string, required: true, doc: "The form param key; the input's text submits under it as-is."
|
|
68
|
+
option :value, :string, doc: "The initial input text."
|
|
69
|
+
option :id, :string, doc: "Stable DOM id token for the root and list ids."
|
|
70
|
+
option :placeholder, :string, doc: "Placeholder text shown while the input is empty."
|
|
71
|
+
option :label, :string, doc: "The accessible name (or wire aria-labelledby via html attrs)."
|
|
72
|
+
option :empty_text, :string, default: "No results.",
|
|
73
|
+
doc: "The no-matches message; hidden while anything matches."
|
|
74
|
+
option :open, :boolean, default: false, doc: "Server-renders the suggestion popup open."
|
|
75
|
+
option :open_on_focus, :boolean, default: true, doc: "Opens the suggestions on focus; false waits for typing."
|
|
76
|
+
|
|
77
|
+
part "autocomplete", "Root wrapper carrying the controller + popper pair"
|
|
78
|
+
part "autocomplete-input", "The REAL text input - role=combobox with aria-expanded " \
|
|
79
|
+
"tracking the popup, the form value itself"
|
|
80
|
+
part "autocomplete-content", "The popper-positioned popup shell",
|
|
81
|
+
states: {
|
|
82
|
+
"data-open" => "popup visible",
|
|
83
|
+
"data-closed" => "popup hidden (the server-rendered default)",
|
|
84
|
+
"data-empty" => "no item matches the query - the empty state shows"
|
|
85
|
+
}
|
|
86
|
+
part "autocomplete-list", "role=listbox holding the options"
|
|
87
|
+
part "autocomplete-item", "One suggestion - role=option; commit writes its label " \
|
|
88
|
+
"(or value:) into the input",
|
|
89
|
+
states: {
|
|
90
|
+
"data-label" => "always - what filtering matches and commit writes",
|
|
91
|
+
"data-value" => "value: given - overrides the committed text",
|
|
92
|
+
"data-highlighted" => "the keyboard/pointer highlight",
|
|
93
|
+
"data-disabled" => "disabled: - skipped by filtering and commit"
|
|
94
|
+
}
|
|
95
|
+
part "autocomplete-empty", "The no-matches message (hidden while anything matches)"
|
|
96
|
+
|
|
97
|
+
# Forces the composition block so with_item calls land before the template renders.
|
|
98
|
+
# @api private
|
|
99
|
+
def before_render
|
|
100
|
+
content
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Hand-rolled builder: ViewComponent lambda slots nil-wrap
|
|
104
|
+
# non-component returns, so the builder collects plain models and
|
|
105
|
+
# before_render forces the composition block.
|
|
106
|
+
|
|
107
|
+
# Adds one suggestion to the popup list.
|
|
108
|
+
#
|
|
109
|
+
# @param label [String] what filtering matches and what commit writes into the input
|
|
110
|
+
# @param value [String, nil] overrides the committed text when it differs from the label
|
|
111
|
+
# @param disabled [Boolean] skipped by filtering and commit
|
|
112
|
+
# @param highlighted [Boolean] server-renders the item highlighted
|
|
113
|
+
def with_item(label:, value: nil, disabled: false, highlighted: false)
|
|
114
|
+
item_models << Item.new(label: label, value: value, disabled: disabled,
|
|
115
|
+
highlighted: highlighted)
|
|
116
|
+
self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The collected suggestion models.
|
|
120
|
+
# @api private
|
|
121
|
+
def item_models = (@item_models ||= [])
|
|
122
|
+
|
|
123
|
+
# @api private
|
|
124
|
+
def autocomplete_id
|
|
125
|
+
@autocomplete_id ||= if (token = dom_id_token(id))
|
|
126
|
+
"poetry-autocomplete-#{token}"
|
|
127
|
+
else
|
|
128
|
+
poetry_instance_id("poetry-autocomplete")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @api private
|
|
133
|
+
def list_id = "#{autocomplete_id}-list"
|
|
134
|
+
|
|
135
|
+
# @api private
|
|
136
|
+
def root_attributes
|
|
137
|
+
html_attributes.merge_if_not_set(
|
|
138
|
+
{ "class" => css, "data-slot" => "autocomplete", "id" => autocomplete_id }
|
|
139
|
+
.merge(stimulus_attributes_for(:root))
|
|
140
|
+
.merge(component_data_attributes)
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @api private
|
|
145
|
+
def input_attributes
|
|
146
|
+
attrs = {
|
|
147
|
+
name: name, value: value, placeholder: placeholder, type: :text,
|
|
148
|
+
autocomplete: "off", role: "combobox",
|
|
149
|
+
"aria-autocomplete": "list", "aria-expanded": open.to_s, "aria-controls": list_id,
|
|
150
|
+
"data-slot": "autocomplete-input"
|
|
151
|
+
}
|
|
152
|
+
attrs[:"aria-label"] = label if label.present?
|
|
153
|
+
attrs.merge(stimulus_attributes_for(:input).transform_keys(&:to_sym))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @api private
|
|
157
|
+
def content_attributes
|
|
158
|
+
attrs = {
|
|
159
|
+
"class" => css(:content), "data-slot" => "autocomplete-content"
|
|
160
|
+
}
|
|
161
|
+
if open
|
|
162
|
+
attrs["data-open"] = ""
|
|
163
|
+
else
|
|
164
|
+
attrs["data-closed"] = ""
|
|
165
|
+
attrs["hidden"] = "hidden"
|
|
166
|
+
end
|
|
167
|
+
attrs.merge(stimulus_attributes_for(:content))
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @api private
|
|
171
|
+
def item_attributes(item, index)
|
|
172
|
+
attrs = {
|
|
173
|
+
"class" => Poetry::Ui::Command::Style.css(:item), "data-slot" => "autocomplete-item",
|
|
174
|
+
"id" => "#{autocomplete_id}-item-#{index}", "role" => "option",
|
|
175
|
+
"data-label" => item.label
|
|
176
|
+
}
|
|
177
|
+
attrs["data-value"] = item.value if item.value.present?
|
|
178
|
+
attrs["data-highlighted"] = "" if item.highlighted
|
|
179
|
+
attrs["data-disabled"] = "" if item.disabled
|
|
180
|
+
attrs["aria-disabled"] = "true" if item.disabled
|
|
181
|
+
attrs.merge(stimulus_attributes_for(:item))
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# One suggestion's plain data model, collected by with_item.
|
|
185
|
+
# @api private
|
|
186
|
+
Item = Struct.new(:label, :value, :disabled, :highlighted, keyword_init: true)
|
|
187
|
+
|
|
188
|
+
private :item_models, :autocomplete_id, :list_id, :root_attributes, :input_attributes, :content_attributes
|
|
189
|
+
private :item_attributes
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module Autocomplete
|
|
6
|
+
# The Autocomplete matrix: the closed hero, the server-rendered open
|
|
7
|
+
# state with a highlight (the contract carrier for data-open /
|
|
8
|
+
# data-highlighted), a value-override item, and the empty state.
|
|
9
|
+
class Preview < Poetry::Core::Preview::Base
|
|
10
|
+
def default
|
|
11
|
+
render_component(name: "tag", placeholder: "e.g. feature", label: "Search tags",
|
|
12
|
+
class: "w-64") do |auto|
|
|
13
|
+
auto.with_item(label: "feature")
|
|
14
|
+
auto.with_item(label: "fix")
|
|
15
|
+
auto.with_item(label: "docs")
|
|
16
|
+
auto.with_item(label: "internal")
|
|
17
|
+
auto.with_item(label: "mobile", disabled: true)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Server-rendered open + highlighted (the states the contract holds).
|
|
22
|
+
def open_and_highlighted
|
|
23
|
+
render_component(name: "city", value: "sp", open: true, label: "City",
|
|
24
|
+
class: "w-64") do |auto|
|
|
25
|
+
auto.with_item(label: "Springfield", highlighted: true)
|
|
26
|
+
auto.with_item(label: "Spokane")
|
|
27
|
+
auto.with_item(label: "São Paulo", value: "Sao Paulo")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# No items at all: the empty message is the whole popup.
|
|
32
|
+
def empty_state
|
|
33
|
+
render_component(name: "search", open: true, label: "Search", open_on_focus: false,
|
|
34
|
+
empty_text: "Nothing matches.", class: "w-64")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module Autocomplete
|
|
6
|
+
# The Autocomplete wears the Combobox/Command visual language (the
|
|
7
|
+
# popup shell and item treatments are the same family upstream);
|
|
8
|
+
# no autocomplete-specific theme rules exist in the upstream
|
|
9
|
+
# dictionaries, so every class here is already-emitted vocabulary.
|
|
10
|
+
class Style < Poetry::Core::Style
|
|
11
|
+
base "relative"
|
|
12
|
+
|
|
13
|
+
element :content, "cn-combobox-content cn-menu-translucent z-50 w-(--anchor-width) " \
|
|
14
|
+
"origin-(--transform-origin) outline-hidden"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# Person images with initials fallbacks.
|
|
6
|
+
module Avatar
|
|
7
|
+
# A person's image over an always-rendered initials fallback. The
|
|
8
|
+
# image layers absolutely above the fallback with an empty alt, so a
|
|
9
|
+
# failed load paints nothing and the initials show through - zero
|
|
10
|
+
# JS, no layout shift. The accessible name lives on the root
|
|
11
|
+
# (role=img + aria-label via label:), never on the layered img.
|
|
12
|
+
#
|
|
13
|
+
# @example Image with initials fallback
|
|
14
|
+
# render Poetry::Ui::Avatar::Component.new(src: user.avatar_url, label: "Ada Lovelace") { "AL" }
|
|
15
|
+
class Component < Poetry::Core::Component
|
|
16
|
+
requires_content "the initials fallback"
|
|
17
|
+
|
|
18
|
+
# The closed vocabulary for the size axis.
|
|
19
|
+
SIZES = %i[default sm lg].freeze
|
|
20
|
+
|
|
21
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
22
|
+
AGENT_RULES = [
|
|
23
|
+
"label: (the person's name) is REQUIRED - it is the avatar's accessible name (role=img).",
|
|
24
|
+
"The content block is the fallback (initials) and is also required - it is what shows " \
|
|
25
|
+
"while the image loads or when it fails.",
|
|
26
|
+
"The badge slot is decorative (a presence dot); put the status meaning in label:, " \
|
|
27
|
+
"not in the badge.",
|
|
28
|
+
"Stack avatars with poetry_avatar_group; the overflow count is poetry_avatar_group_count."
|
|
29
|
+
].freeze
|
|
30
|
+
|
|
31
|
+
renders_one :badge, doc: "Decorative presence dot, bottom-right; keep the status meaning in label:."
|
|
32
|
+
|
|
33
|
+
option :src, :string, doc: "The image URL; without it only the initials fallback shows."
|
|
34
|
+
option :label, :string, required: true,
|
|
35
|
+
doc: "The person's name - the avatar's accessible name (blank raises). The required " \
|
|
36
|
+
"flag also carries the fact to the registry so static checks see it."
|
|
37
|
+
option :size, :symbol, default: :default, doc: "The diameter axis."
|
|
38
|
+
|
|
39
|
+
validates :size, inclusion: { in: SIZES }
|
|
40
|
+
|
|
41
|
+
part "avatar", "Root span (role=img carrying the accessible name) - fallback, image, " \
|
|
42
|
+
"and badge layer inside it",
|
|
43
|
+
states: {
|
|
44
|
+
"data-size" => { condition: "always - the resolved size", values: SIZES.map(&:to_s) }
|
|
45
|
+
}
|
|
46
|
+
part "avatar-fallback", "The initials layer (the content block) - always in the DOM, " \
|
|
47
|
+
"showing until the image covers it"
|
|
48
|
+
part "avatar-image", "The <img> layered absolutely over the fallback - only when src: " \
|
|
49
|
+
"is given; a failed load paints nothing"
|
|
50
|
+
part "avatar-badge", "The decorative presence dot (the badge slot), bottom-right"
|
|
51
|
+
|
|
52
|
+
# Enforces label: and the initials content block.
|
|
53
|
+
# @api private
|
|
54
|
+
def before_render
|
|
55
|
+
raise ArgumentError, "Avatar requires label: (the person's name - its accessible name)" if label.blank?
|
|
56
|
+
|
|
57
|
+
ensure_content!
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api private
|
|
61
|
+
def call
|
|
62
|
+
content_tag(:span, root_attributes.to_attributes) do
|
|
63
|
+
safe_join([fallback, image, badge_part].compact)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @api private
|
|
68
|
+
def root_attributes
|
|
69
|
+
html_attributes.merge_if_not_set(
|
|
70
|
+
{
|
|
71
|
+
"data-slot" => "avatar", "data-size" => size,
|
|
72
|
+
"role" => "img", "aria-label" => label
|
|
73
|
+
}.merge(component_data_attributes)
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def fallback
|
|
80
|
+
content_tag(:span, content, "data-slot" => "avatar-fallback", "aria-hidden" => "true",
|
|
81
|
+
class: css(:fallback))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def image
|
|
85
|
+
return if src.blank?
|
|
86
|
+
|
|
87
|
+
# alt="" on purpose: the root carries the name, and an empty alt
|
|
88
|
+
# keeps a FAILED load from painting a broken-image glyph over the
|
|
89
|
+
# initials - the layered-fallback contract.
|
|
90
|
+
tag.img(src: src, alt: "", "data-slot": "avatar-image", class: css(:image))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def badge_part
|
|
94
|
+
return unless badge?
|
|
95
|
+
|
|
96
|
+
content_tag(:span, badge, "data-slot" => "avatar-badge", "aria-hidden" => "true", class: css(:badge))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private :root_attributes
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%# The stacked group: overlapping avatars ringed by the background, with
|
|
2
|
+
the overflow count - the poetry_avatar_group / _group_count part
|
|
3
|
+
helpers. %>
|
|
4
|
+
<div class="p-4">
|
|
5
|
+
<%= poetry_avatar_group do %>
|
|
6
|
+
<%= poetry_avatar(label: "Grace Hopper") { "GH" } %>
|
|
7
|
+
<%= poetry_avatar(label: "Ada Lovelace") { "AL" } %>
|
|
8
|
+
<%= poetry_avatar(label: "Annie Easley") { "AE" } %>
|
|
9
|
+
<%= poetry_avatar_group_count { "+3" } %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|