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,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module Drawer
|
|
6
|
+
# The Drawer preview: the bottom sheet with its grab handle (the
|
|
7
|
+
# gesture's discoverability affordance), and a right-edge panel.
|
|
8
|
+
# Previews render CLOSED (the trigger page, the Dialog convention);
|
|
9
|
+
# the dommy tier drives the open/swipe behavior.
|
|
10
|
+
class Preview < Poetry::Core::Preview::Base
|
|
11
|
+
def default
|
|
12
|
+
render_component(show_swipe_handle: true) do |drawer|
|
|
13
|
+
drawer.with_trigger { "Open drawer" }
|
|
14
|
+
drawer.with_title { "Move goal" }
|
|
15
|
+
drawer.with_description { "Set your daily activity goal." }
|
|
16
|
+
drawer.with_footer do
|
|
17
|
+
embed(Poetry::Ui::Button::Component.new.with_content("Submit"))
|
|
18
|
+
end
|
|
19
|
+
"Drag the handle down to dismiss."
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def right_edge
|
|
24
|
+
render_component(direction: :right) do |drawer|
|
|
25
|
+
drawer.with_trigger { "Open panel" }
|
|
26
|
+
drawer.with_title { "Filters" }
|
|
27
|
+
"Swipe right to dismiss."
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def top_sheet
|
|
32
|
+
render_component(direction: :up) do |drawer|
|
|
33
|
+
drawer.with_trigger { "Open notice" }
|
|
34
|
+
drawer.with_title { "Service notice" }
|
|
35
|
+
"Swipe up to dismiss."
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def left_edge
|
|
40
|
+
render_component(direction: :left) do |drawer|
|
|
41
|
+
drawer.with_trigger { "Open navigation" }
|
|
42
|
+
drawer.with_title { "Navigate" }
|
|
43
|
+
"Swipe left to dismiss."
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# modal: false keeps the page interactive behind the drawer -
|
|
48
|
+
# Esc rides its own keydown exit (a non-modal dialog never fires
|
|
49
|
+
# cancel).
|
|
50
|
+
def non_modal
|
|
51
|
+
render_component(modal: false) do |drawer|
|
|
52
|
+
drawer.with_trigger { "Open player" }
|
|
53
|
+
drawer.with_title { "Now playing" }
|
|
54
|
+
"The page stays interactive behind this drawer."
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# snap_points: the swipe physics settle at fractional heights.
|
|
59
|
+
def with_snap_points
|
|
60
|
+
render_component(snap_points: [0.4, 1]) do |drawer|
|
|
61
|
+
drawer.with_trigger { "Open details" }
|
|
62
|
+
drawer.with_title { "Details" }
|
|
63
|
+
"Drag the handle - the sheet settles at 40% or full height."
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module Drawer
|
|
6
|
+
# Re-expressed through the cn-* theme layer, still the
|
|
7
|
+
# native-dialog swipe spine. The ENTIRE swipe mechanism stays inline
|
|
8
|
+
# (transform vars, swipe-progress backdrop math, starting/ending
|
|
9
|
+
# transforms, the direction margin geometry + closed-transform vars)
|
|
10
|
+
# - a swapped theme can restyle surfaces but never break the gesture.
|
|
11
|
+
# Margins stay dictionary-side ON BOTH content and directions so the
|
|
12
|
+
# merger keeps collapsing m-0 against the side margins exactly as
|
|
13
|
+
# before (the split-side conflict rule, applied by keeping the
|
|
14
|
+
# conflict on the inline side this time). Corners/edges/surface ride
|
|
15
|
+
# the theme. Snap points landed 2026-08-01; nested-stack visuals
|
|
16
|
+
# and bleed stay deferred.
|
|
17
|
+
class Style < Poetry::Core::Style
|
|
18
|
+
# open:flex, not flex: never defeat the UA's dialog:not([open])
|
|
19
|
+
# display:none (the Dialog browser-pass lesson).
|
|
20
|
+
# Scrim color + blur are theme-owned: each
|
|
21
|
+
# themes/<name>.css carries backdrop:bg-* / backdrop-blur-* on
|
|
22
|
+
# .cn-drawer-content. Only the swipe machinery (transition +
|
|
23
|
+
# opacity calc) stays structural here.
|
|
24
|
+
# No `relative`: the top layer discards it and the UA reasserts
|
|
25
|
+
# `absolute`, pinning the drawer to the DOCUMENT edge (it scrolls
|
|
26
|
+
# off-screen below the fold). The UA `:modal` rule keeps it
|
|
27
|
+
# viewport-fixed so the direction auto-margins pin it to the viewport
|
|
28
|
+
# edge; the swipe transform rides on top.
|
|
29
|
+
element :content,
|
|
30
|
+
"cn-drawer-content m-0 open:flex min-h-0 flex-col " \
|
|
31
|
+
"outline-none select-none will-change-transform " \
|
|
32
|
+
"transition-[transform,opacity] duration-450 ease-[cubic-bezier(0.22,1,0.36,1)] " \
|
|
33
|
+
"transform-[translate3d(var(--translate-x,0px),var(--translate-y,0px),0)] " \
|
|
34
|
+
"data-swiping:duration-0 " \
|
|
35
|
+
"data-starting-style:transform-(--closed-transform) " \
|
|
36
|
+
"data-ending-style:transform-(--closed-transform) " \
|
|
37
|
+
"data-ending-style:duration-[calc(var(--drawer-swipe-strength,1)*400ms)] " \
|
|
38
|
+
"backdrop:transition-opacity backdrop:duration-450 " \
|
|
39
|
+
"backdrop:opacity-[calc(1-var(--drawer-swipe-progress,0))] " \
|
|
40
|
+
"data-swiping:backdrop:transition-none"
|
|
41
|
+
|
|
42
|
+
# The dismiss-direction geometry: top-layer edge margins + the
|
|
43
|
+
# direction's closed transform, with the controller's positive
|
|
44
|
+
# movement mapped onto the axis (negative directions invert it).
|
|
45
|
+
# The corner/edge treatment rides cn-drawer-direction-*.
|
|
46
|
+
# --drawer-inset (theme-owned; upstream's "floats the drawer from
|
|
47
|
+
# the viewport edges", default 0px = flush) gaps every non-anchored
|
|
48
|
+
# edge: the anchored side swaps its 0-margin for the inset, the
|
|
49
|
+
# cross axis swaps its explicit size (w-full/h-full) for auto so
|
|
50
|
+
# the UA's inset:0 over-constraint fills viewport-minus-margins
|
|
51
|
+
# (identical to the full size at 0px), and the closed transform
|
|
52
|
+
# travels the extra inset so the exit still clears the viewport.
|
|
53
|
+
# rhea/mira/luma/maia ship --spacing(2), like their source styles.
|
|
54
|
+
variant :direction, {
|
|
55
|
+
# down/up wear h-fit, never h-auto: the UA :modal sets top:0 AND
|
|
56
|
+
# bottom:0, and an abspos box with both insets and height:auto
|
|
57
|
+
# SOLVES height to fill (CSS2 10.6.4) - h-auto blew every bottom
|
|
58
|
+
# sheet up to its max-h cap. fit-content restores the UA's own
|
|
59
|
+
# content sizing; the auto margin still bottom-anchors. The
|
|
60
|
+
# x-axis directions keep h-auto: there fill IS the intent, the
|
|
61
|
+
# edge panel runs the full viewport.
|
|
62
|
+
down: "cn-drawer-direction-down mt-auto mb-(--drawer-inset,0px) mx-(--drawer-inset,0px) " \
|
|
63
|
+
"h-fit w-auto max-w-none max-h-[calc(100dvh-6rem)] " \
|
|
64
|
+
"data-snap-points:h-dvh data-snap-points:max-h-none " \
|
|
65
|
+
"[--closed-transform:translate3d(0,calc(100%+var(--drawer-inset,0px)+2px),0)] " \
|
|
66
|
+
"[--translate-y:calc(var(--drawer-snap-point-offset,0px)+var(--drawer-swipe-movement-y,0px))]",
|
|
67
|
+
up: "cn-drawer-direction-up mb-auto mt-(--drawer-inset,0px) mx-(--drawer-inset,0px) " \
|
|
68
|
+
"h-fit w-auto max-w-none max-h-[calc(100dvh-6rem)] " \
|
|
69
|
+
"[--closed-transform:translate3d(0,calc(-100%-var(--drawer-inset,0px)-2px),0)] " \
|
|
70
|
+
"[--translate-y:calc(-1*var(--drawer-swipe-movement-y,0px))]",
|
|
71
|
+
left: "cn-drawer-direction-left mr-auto ml-(--drawer-inset,0px) my-(--drawer-inset,0px) " \
|
|
72
|
+
"h-auto max-h-none w-3/4 sm:max-w-sm " \
|
|
73
|
+
"[--closed-transform:translate3d(calc(-100%-var(--drawer-inset,0px)-2px),0,0)] " \
|
|
74
|
+
"[--translate-x:calc(-1*var(--drawer-swipe-movement-x,0px))]",
|
|
75
|
+
right: "cn-drawer-direction-right ml-auto mr-(--drawer-inset,0px) my-(--drawer-inset,0px) " \
|
|
76
|
+
"h-auto max-h-none w-3/4 sm:max-w-sm " \
|
|
77
|
+
"[--closed-transform:translate3d(calc(100%+var(--drawer-inset,0px)+2px),0,0)] " \
|
|
78
|
+
"[--translate-x:var(--drawer-swipe-movement-x,0px)]"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# The grab pill (touch-none keeps the browser from stealing the
|
|
82
|
+
# drag for scroll); the pill's look rides the theme rule.
|
|
83
|
+
element :handle, "cn-drawer-swipe-handle relative z-10 mx-auto flex h-3 w-full shrink-0 " \
|
|
84
|
+
"cursor-grab touch-none items-center justify-center active:cursor-grabbing " \
|
|
85
|
+
"after:block after:shrink-0"
|
|
86
|
+
|
|
87
|
+
element :header, "cn-drawer-header flex shrink-0 flex-col"
|
|
88
|
+
# Non-modal show() skips the top layer; the UA :modal positioning
|
|
89
|
+
# must be restated (dictionary-resident so safelists ship it).
|
|
90
|
+
element :nonmodal, "fixed inset-0 z-50"
|
|
91
|
+
element :footer, "cn-drawer-footer mt-auto flex shrink-0 flex-col"
|
|
92
|
+
element :title, "cn-drawer-title"
|
|
93
|
+
element :description, "cn-drawer-description text-balance"
|
|
94
|
+
element :body, "cn-drawer-body flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain"
|
|
95
|
+
|
|
96
|
+
# The direction's edge classes for the <dialog> element (the Sheet
|
|
97
|
+
# resolver pattern: variants render at the dictionary root, and the
|
|
98
|
+
# Drawer's visual root is the :content element).
|
|
99
|
+
def self.direction(value)
|
|
100
|
+
resolver.variants.fetch(:direction).fetch(value.to_sym)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# The DropdownMenu family - the button-triggered action menu.
|
|
6
|
+
module DropdownMenu
|
|
7
|
+
# The closed vocabulary for the side placement axis.
|
|
8
|
+
SIDES = %i[top right bottom left].freeze
|
|
9
|
+
# The closed vocabulary for the align placement axis.
|
|
10
|
+
ALIGNS = %i[start center end].freeze
|
|
11
|
+
# The closed vocabulary for the dir (reading direction) axis.
|
|
12
|
+
DIRS = %i[ltr rtl].freeze
|
|
13
|
+
|
|
14
|
+
# The shared menus item union wearing this family's identity -
|
|
15
|
+
# SLOT_BUILDERS is added in a reopen at the bottom of the file,
|
|
16
|
+
# once the family classes it names exist.
|
|
17
|
+
module ItemSlots
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
include Poetry::Ui::Menus::ItemSlots
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A dropdown menu: a button that opens a popup list of actions
|
|
23
|
+
# (role=menu), composed from items, checkbox items, radio groups,
|
|
24
|
+
# labels, separators, groups, and nested submenus. Reach for it
|
|
25
|
+
# when one control offers several actions; choosing a form VALUE
|
|
26
|
+
# belongs to Select or Combobox instead.
|
|
27
|
+
#
|
|
28
|
+
# Requires with_trigger (the menu button) and at least one item.
|
|
29
|
+
# Arrow-key navigation, typeahead, and focus containment activate
|
|
30
|
+
# when the menu opens; the closed menu renders inert and hidden.
|
|
31
|
+
#
|
|
32
|
+
# @example A menu button with actions
|
|
33
|
+
# render Poetry::Ui::DropdownMenu::Component.new do |menu|
|
|
34
|
+
# menu.with_trigger(variant: :outline) { "Open" }
|
|
35
|
+
# menu.with_item { "Rename" }
|
|
36
|
+
# menu.with_item(variant: :destructive) { "Delete" }
|
|
37
|
+
# end
|
|
38
|
+
class Component < Poetry::Core::Component
|
|
39
|
+
include Poetry::Ui::ComposableTrigger
|
|
40
|
+
|
|
41
|
+
include ItemSlots
|
|
42
|
+
|
|
43
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
44
|
+
AGENT_RULES = [
|
|
45
|
+
ComposableTrigger::AGENT_RULE,
|
|
46
|
+
"Use poetry_dropdown_menu - never hand-roll role=menu popups with Tailwind.",
|
|
47
|
+
"Items are ACTIONS. Choosing a form VALUE is a Select/Combobox - do not fake it with radio items.",
|
|
48
|
+
"Navigation items pass with_item(href:) (external: for a new tab); a form action (sign-out, a " \
|
|
49
|
+
"DELETE) passes with_item(submit:, method:). The item renders AS the anchor / submit button " \
|
|
50
|
+
"(role=menuitem on the <a> or <button>) - one interactive element - so NEVER nest a link_to or " \
|
|
51
|
+
"button_to inside an item.",
|
|
52
|
+
"Icon-only triggers MUST have an accessible name (the composed Button's label: rule).",
|
|
53
|
+
"Never write the state attributes (data-popup-open / data-checked / data-unchecked) without " \
|
|
54
|
+
"their aria twin (aria-expanded / aria-checked) - the controller writes both; agents patching " \
|
|
55
|
+
"DOM must too.",
|
|
56
|
+
"Destructive items use variant: :destructive AND still confirm irreversible actions via a dialog.",
|
|
57
|
+
"shortcut: is a visual hint only - it does NOT bind the key; wire a real hotkey separately or omit it.",
|
|
58
|
+
"Do not nest interactive elements inside items (a menuitem IS the interactive unit).",
|
|
59
|
+
"Keep submenus <= 2 levels; prefer grouping + separators over deep nesting.",
|
|
60
|
+
"Critical actions must exist somewhere reachable without JS (menus are JS-required interaction)."
|
|
61
|
+
].freeze
|
|
62
|
+
|
|
63
|
+
# Slot-to-component map: with_trigger renders a Button, so callers
|
|
64
|
+
# get Button's full option and slot contract on the trigger.
|
|
65
|
+
SLOT_RENDERS = { trigger: Button::Component }.freeze
|
|
66
|
+
|
|
67
|
+
renders_one :trigger,
|
|
68
|
+
doc: "The menu button - a poetry Button (options forward to it, e.g. variant: :outline). The " \
|
|
69
|
+
"slot owns the aria-haspopup/expanded/ controls wiring regardless of the composed content, " \
|
|
70
|
+
"so composition cannot drop the aria.",
|
|
71
|
+
renders: lambda { |**options, &block|
|
|
72
|
+
wiring = {
|
|
73
|
+
"id" => trigger_id, "data-slot" => "dropdown-menu-trigger",
|
|
74
|
+
"aria-haspopup" => "menu", "aria-expanded" => open.to_s, "aria-controls" => content_id
|
|
75
|
+
}.merge(stimulus_attributes_for(:trigger))
|
|
76
|
+
# Trigger state: bare data-popup-open while open, NO attribute
|
|
77
|
+
# while closed (absence IS the state).
|
|
78
|
+
wiring["data-popup-open"] = "" if open
|
|
79
|
+
composed_trigger(wiring, options, &block) || begin
|
|
80
|
+
options[:disabled] = true if disabled && !options.key?(:disabled)
|
|
81
|
+
Button::Component.new(**wiring, **options, &block)
|
|
82
|
+
end
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# The content's layer behaviors (focus containment, dismissal,
|
|
86
|
+
# roving focus) are attached by the menu controller on open rather
|
|
87
|
+
# than declared statically here - a statically-connected focus
|
|
88
|
+
# trap on a hidden menu would steal focus at page load.
|
|
89
|
+
use_stimulus do
|
|
90
|
+
on :root do
|
|
91
|
+
controller :menu do
|
|
92
|
+
register
|
|
93
|
+
value :open
|
|
94
|
+
value :modal
|
|
95
|
+
value :loop
|
|
96
|
+
end
|
|
97
|
+
controller :popper do
|
|
98
|
+
register
|
|
99
|
+
value :side
|
|
100
|
+
value :align
|
|
101
|
+
value :side_offset
|
|
102
|
+
value :align_offset
|
|
103
|
+
value :avoid_collisions
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
on :trigger do
|
|
107
|
+
controller :menu do
|
|
108
|
+
action :toggle, on: :click
|
|
109
|
+
action :trigger_keydown, on: :keydown
|
|
110
|
+
end
|
|
111
|
+
controller(:popper) { target :anchor }
|
|
112
|
+
end
|
|
113
|
+
on :content do
|
|
114
|
+
controller(:popper) { target :content }
|
|
115
|
+
end
|
|
116
|
+
# Anatomy-rendered wiring, declared here so the contract covers
|
|
117
|
+
# the whole family surface (items + submenus render via the
|
|
118
|
+
# escape hatch in the anatomy classes).
|
|
119
|
+
on :item do
|
|
120
|
+
controller(:menu) { action :activate, on: :click }
|
|
121
|
+
end
|
|
122
|
+
on :sub_trigger do
|
|
123
|
+
controller :menu do
|
|
124
|
+
action :sub_enter, on: :pointerenter
|
|
125
|
+
action :sub_leave, on: :pointerleave
|
|
126
|
+
action :open_sub, on: :click
|
|
127
|
+
end
|
|
128
|
+
controller(:popper) { target :anchor }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
option :open, :boolean, default: false, doc: "Renders the menu already open on page load."
|
|
133
|
+
option :modal, :boolean, default: true,
|
|
134
|
+
doc: "While open, pointer interaction outside the menu is blocked; false keeps the " \
|
|
135
|
+
"rest of the page interactive."
|
|
136
|
+
option :side, :symbol, default: :bottom,
|
|
137
|
+
doc: "Which side of the trigger the menu opens on (flips on collision)."
|
|
138
|
+
option :align, :symbol, default: :center, doc: "The menu's alignment against the trigger's edge."
|
|
139
|
+
option :side_offset, :integer, default: 4, doc: "Gap in pixels between the trigger and the menu."
|
|
140
|
+
option :align_offset, :integer, default: 0, doc: "Pixel shift along the alignment edge."
|
|
141
|
+
option :avoid_collisions, :boolean, default: true,
|
|
142
|
+
doc: "Flips/shifts placement to keep the menu inside the viewport."
|
|
143
|
+
option :loop, :boolean, default: false, doc: "Arrow-key navigation wraps from the last item back to the first."
|
|
144
|
+
option :dir, :symbol, doc: "Reading direction; :rtl flips submenu sides and indicators."
|
|
145
|
+
option :disabled, :boolean, default: false, doc: "Disables the menu trigger button."
|
|
146
|
+
|
|
147
|
+
validates :side, inclusion: { in: SIDES }
|
|
148
|
+
validates :align, inclusion: { in: ALIGNS }
|
|
149
|
+
validates :dir, inclusion: { in: DIRS }, allow_nil: true
|
|
150
|
+
|
|
151
|
+
# (dropdown-menu-trigger rides the composed Button, so that element
|
|
152
|
+
# belongs to Button's anatomy, not this contract.)
|
|
153
|
+
part "dropdown-menu", "Root wrapper hosting the menu + popper controllers around the trigger " \
|
|
154
|
+
"and content"
|
|
155
|
+
part "dropdown-menu-content", "The role=menu popup panel - positioning, animation, and the " \
|
|
156
|
+
"open state ride here",
|
|
157
|
+
states: {
|
|
158
|
+
"data-open" => "menu is open (presence flips the pair at runtime)",
|
|
159
|
+
"data-closed" => "menu is closed or animating out (the server-rendered state)",
|
|
160
|
+
"data-side" => { condition: "the placement side (popper re-writes it after collision flips)",
|
|
161
|
+
values: %w[top right bottom left] },
|
|
162
|
+
"data-align" => { condition: "the alignment against the trigger (popper re-resolves it)",
|
|
163
|
+
values: %w[start center end] }
|
|
164
|
+
},
|
|
165
|
+
vars: {
|
|
166
|
+
"--transform-origin" => "popper's anchor-facing animation origin",
|
|
167
|
+
"--available-width" => "popper: viewport space left for the panel (post-flip)",
|
|
168
|
+
"--available-height" => "popper: viewport space left for the panel (post-flip)",
|
|
169
|
+
"--anchor-width" => "popper: the trigger's measured width",
|
|
170
|
+
"--anchor-height" => "popper: the trigger's measured height"
|
|
171
|
+
}
|
|
172
|
+
part "dropdown-menu-group", "role=group semantic grouping between separators"
|
|
173
|
+
part "dropdown-menu-label", "Non-interactive heading for a run of items",
|
|
174
|
+
states: {
|
|
175
|
+
"data-inset" => "indented to align with checkbox/radio item text (inset: true)"
|
|
176
|
+
}
|
|
177
|
+
part "dropdown-menu-item", "One role=menuitem action row",
|
|
178
|
+
states: {
|
|
179
|
+
"data-variant" => "default or destructive (the danger treatment)",
|
|
180
|
+
"data-inset" => "indented to align with checkbox/radio item text (inset: true)",
|
|
181
|
+
"data-disabled" => "item is disabled (always written together with aria-disabled)"
|
|
182
|
+
}
|
|
183
|
+
part "dropdown-menu-checkbox-item", "A role=menuitemcheckbox toggle row",
|
|
184
|
+
states: {
|
|
185
|
+
"data-checked" => "checked (the controller re-writes the pair with aria-checked on " \
|
|
186
|
+
"activation)",
|
|
187
|
+
"data-unchecked" => "unchecked",
|
|
188
|
+
"data-disabled" => "item is disabled (always written together with aria-disabled)",
|
|
189
|
+
"data-close-on-select" => "per-item override of the menu's close-on-select default " \
|
|
190
|
+
"(\"false\" keeps the menu open)"
|
|
191
|
+
}
|
|
192
|
+
part "dropdown-menu-radio-group", "role=group scoping one single-select value",
|
|
193
|
+
states: {
|
|
194
|
+
"data-value" => "the selected radio value (the controller re-writes it on change)"
|
|
195
|
+
}
|
|
196
|
+
part "dropdown-menu-radio-item", "A role=menuitemradio row inside a radio group",
|
|
197
|
+
states: {
|
|
198
|
+
"data-checked" => "the selected radio (the controller re-writes the pair with aria-checked)",
|
|
199
|
+
"data-unchecked" => "not selected",
|
|
200
|
+
"data-value" => "the radio's value",
|
|
201
|
+
"data-disabled" => "item is disabled (always written together with aria-disabled)"
|
|
202
|
+
}
|
|
203
|
+
part "dropdown-menu-checkbox-item-indicator",
|
|
204
|
+
"The check glyph inside checkbox items (aria-hidden; the item carries the checked state)"
|
|
205
|
+
part "dropdown-menu-radio-item-indicator",
|
|
206
|
+
"The circle glyph inside radio items (aria-hidden; the item carries the checked state)"
|
|
207
|
+
part "dropdown-menu-separator", "role=separator rule between groups"
|
|
208
|
+
part "dropdown-menu-shortcut", "The trailing keybinding HINT - aria-hidden, never binds the key"
|
|
209
|
+
part "dropdown-menu-sub", "A submenu scope - hosts its own popper around the sub trigger/" \
|
|
210
|
+
"content pair"
|
|
211
|
+
part "dropdown-menu-sub-trigger", "The role=menuitem row opening its submenu",
|
|
212
|
+
states: {
|
|
213
|
+
"data-popup-open" => "its submenu is open (written with aria-expanded; absence is the " \
|
|
214
|
+
"closed state)",
|
|
215
|
+
"data-inset" => "indented to align with checkbox/radio item text (inset: true)"
|
|
216
|
+
}
|
|
217
|
+
part "dropdown-menu-sub-content", "The nested role=menu panel - its own popper content on the " \
|
|
218
|
+
"same presence machinery",
|
|
219
|
+
states: {
|
|
220
|
+
"data-open" => "submenu is open (presence flips the pair at runtime)",
|
|
221
|
+
"data-closed" => "submenu is closed (the server-rendered state)",
|
|
222
|
+
"data-side" => { condition: "the placement side (right/left by direction; popper resolves " \
|
|
223
|
+
"it at runtime)",
|
|
224
|
+
values: %w[top right bottom left] },
|
|
225
|
+
"data-align" => { condition: "the alignment against the sub-trigger (popper resolves it " \
|
|
226
|
+
"at runtime)",
|
|
227
|
+
values: %w[start center end] }
|
|
228
|
+
},
|
|
229
|
+
vars: {
|
|
230
|
+
"--transform-origin" => "popper's anchor-facing animation origin",
|
|
231
|
+
"--available-width" => "popper: viewport space left for the panel (post-flip)",
|
|
232
|
+
"--available-height" => "popper: viewport space left for the panel (post-flip)",
|
|
233
|
+
"--anchor-width" => "popper: the sub-trigger's measured width",
|
|
234
|
+
"--anchor-height" => "popper: the sub-trigger's measured height"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Enforces the required slots before render.
|
|
238
|
+
# @api private
|
|
239
|
+
def before_render
|
|
240
|
+
raise ArgumentError, "DropdownMenu requires with_trigger (the menu button)" unless trigger?
|
|
241
|
+
raise ArgumentError, "DropdownMenu requires at least one item" unless items?
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# The trigger element's server-stable id.
|
|
245
|
+
# @api private
|
|
246
|
+
def trigger_id
|
|
247
|
+
"#{instance_id}-trigger"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# The content element's server-stable id (aria-controls target).
|
|
251
|
+
# @api private
|
|
252
|
+
def content_id
|
|
253
|
+
"#{instance_id}-content"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# The root wrapper's attributes.
|
|
257
|
+
# @api private
|
|
258
|
+
def root_attributes
|
|
259
|
+
root = { "data-slot" => "dropdown-menu" }
|
|
260
|
+
root["dir"] = dir.to_s if dir
|
|
261
|
+
html_attributes.merge_if_not_set(
|
|
262
|
+
root.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
|
|
263
|
+
)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# The role=menu panel's attributes.
|
|
267
|
+
# @api private
|
|
268
|
+
def content_attributes
|
|
269
|
+
attrs = {
|
|
270
|
+
"id" => content_id, "role" => "menu", "aria-orientation" => "vertical",
|
|
271
|
+
"aria-labelledby" => trigger_id, "tabindex" => "-1",
|
|
272
|
+
"data-slot" => "dropdown-menu-content", (open ? "data-open" : "data-closed") => "",
|
|
273
|
+
# Initial placement, re-resolved live by popper on open.
|
|
274
|
+
"data-side" => side, "data-align" => align,
|
|
275
|
+
"class" => css(:content)
|
|
276
|
+
}.merge(stimulus_attributes_for(:content))
|
|
277
|
+
attrs["hidden"] = true unless open
|
|
278
|
+
attrs
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
private
|
|
282
|
+
|
|
283
|
+
def menu_dir
|
|
284
|
+
dir
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Server-stable unique id pair for the aria wiring (two menus on
|
|
288
|
+
# one page must not share ids); portal-safe (the controller
|
|
289
|
+
# resolves content via aria-controls, not a Stimulus target).
|
|
290
|
+
def instance_id
|
|
291
|
+
@instance_id ||= poetry_instance_id("poetry-dropdown-menu")
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
private :trigger_id, :content_id, :root_attributes, :content_attributes
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# role=group semantic grouping between separators - the shared kernel
|
|
298
|
+
# anatomy wearing this family identity.
|
|
299
|
+
#
|
|
300
|
+
# @api private
|
|
301
|
+
class Group < Poetry::Ui::Menus::Group
|
|
302
|
+
include ItemSlots
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# role=group scoping the single-select value for its radio items -
|
|
306
|
+
# the shared kernel anatomy wearing this family identity.
|
|
307
|
+
#
|
|
308
|
+
# @api private
|
|
309
|
+
class RadioGroup < Poetry::Ui::Menus::RadioGroup
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# A submenu scope (recursive item union on its own popper) - the
|
|
313
|
+
# shared kernel anatomy wearing this family identity.
|
|
314
|
+
#
|
|
315
|
+
# @api private
|
|
316
|
+
class Sub < Poetry::Ui::Menus::Sub
|
|
317
|
+
include ItemSlots
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# The builder classes behind lambda-wrapped slot types: a lambda
|
|
321
|
+
# hides its return class from introspection, so the owner declares
|
|
322
|
+
# it and the registry walker recurses into the builder's own call
|
|
323
|
+
# surface (with_sub yields a Sub with its own items).
|
|
324
|
+
module ItemSlots
|
|
325
|
+
# Nested-slot recursion map for the registry projection.
|
|
326
|
+
SLOT_BUILDERS = { sub: Sub, group: Group, radio_group: RadioGroup }.freeze
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
class Component
|
|
330
|
+
# The slots before_render enforces, stated statically for render-free checks.
|
|
331
|
+
REQUIRED_SLOTS = { trigger: "the menu button", item: "at least one item" }.freeze
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
class Group
|
|
335
|
+
# The slots before_render enforces, stated statically for render-free checks.
|
|
336
|
+
REQUIRED_SLOTS = { item: "at least one item" }.freeze
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
class RadioGroup
|
|
340
|
+
# The slots before_render enforces, stated statically for render-free checks.
|
|
341
|
+
REQUIRED_SLOTS = { radio_item: "at least one radio item" }.freeze
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
class Sub
|
|
345
|
+
# The slots before_render enforces, stated statically for render-free checks.
|
|
346
|
+
REQUIRED_SLOTS = { trigger: "the sub-menu item", item: "at least one item" }.freeze
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DropdownMenu
|
|
6
|
+
# The DropdownMenu preview matrix: the ported demo composition plus
|
|
7
|
+
# both item variants, both toggle kinds, inset, disabled, a 2-deep
|
|
8
|
+
# sub, RTL, and the server-rendered open state.
|
|
9
|
+
class Preview < Poetry::Core::Preview::Base
|
|
10
|
+
# The dropdown-menu-demo port: Button trigger, label, grouped items
|
|
11
|
+
# with shortcuts, a submenu, a disabled item, a destructive tail.
|
|
12
|
+
def default
|
|
13
|
+
render_component(align: :start) do |menu|
|
|
14
|
+
menu.with_trigger(variant: :outline) { "Open" }
|
|
15
|
+
menu.with_label { "My Account" }
|
|
16
|
+
menu.with_group do |group|
|
|
17
|
+
group.with_item(shortcut: "⇧⌘P") { "Profile" }
|
|
18
|
+
group.with_item(shortcut: "⌘B") { "Billing" }
|
|
19
|
+
group.with_item(shortcut: "⌘S") { "Settings" }
|
|
20
|
+
end
|
|
21
|
+
menu.with_separator
|
|
22
|
+
menu.with_sub do |sub|
|
|
23
|
+
sub.with_trigger { "Invite users" }
|
|
24
|
+
sub.with_item { "Email" }
|
|
25
|
+
sub.with_item { "Message" }
|
|
26
|
+
sub.with_sub do |nested|
|
|
27
|
+
nested.with_trigger { "More..." }
|
|
28
|
+
nested.with_item { "Import from CSV" }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
menu.with_separator
|
|
32
|
+
menu.with_item(disabled: true) { "API" }
|
|
33
|
+
menu.with_item(variant: :destructive, shortcut: "⇧⌘Q") { "Log out" }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# dropdown-menu-checkboxes parity: toggles keep the menu open.
|
|
38
|
+
def checkboxes
|
|
39
|
+
render_component(align: :start) do |menu|
|
|
40
|
+
menu.with_trigger(variant: :outline) { "View" }
|
|
41
|
+
menu.with_label { "Appearance" }
|
|
42
|
+
menu.with_separator
|
|
43
|
+
menu.with_checkbox_item(checked: true, close_on_select: false) { "Status Bar" }
|
|
44
|
+
menu.with_checkbox_item(checked: false, close_on_select: false, disabled: true) { "Activity Bar" }
|
|
45
|
+
menu.with_checkbox_item(checked: false, close_on_select: false) { "Panel" }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# dropdown-menu-radio-group parity: one checked value per group.
|
|
50
|
+
def radio_group
|
|
51
|
+
render_component(align: :start) do |menu|
|
|
52
|
+
menu.with_trigger(variant: :outline) { "Panel Position" }
|
|
53
|
+
menu.with_label { "Panel Position" }
|
|
54
|
+
menu.with_separator
|
|
55
|
+
menu.with_radio_group(value: "bottom") do |group|
|
|
56
|
+
group.with_radio_item(value: "top") { "Top" }
|
|
57
|
+
group.with_radio_item(value: "bottom") { "Bottom" }
|
|
58
|
+
group.with_radio_item(value: "right") { "Right" }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# inset aligns flush items with the checkbox/radio gutter.
|
|
64
|
+
def inset_and_disabled
|
|
65
|
+
render_component do |menu|
|
|
66
|
+
menu.with_trigger(variant: :outline) { "Edit" }
|
|
67
|
+
menu.with_label(inset: true) { "Actions" }
|
|
68
|
+
menu.with_item(inset: true) { "Undo" }
|
|
69
|
+
menu.with_item(inset: true, disabled: true) { "Redo" }
|
|
70
|
+
menu.with_separator
|
|
71
|
+
menu.with_checkbox_item(checked: true, close_on_select: false) { "Autosave" }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The submenu side flips under RTL (chevron mirrors via ml-auto).
|
|
76
|
+
def rtl
|
|
77
|
+
render_component(dir: :rtl) do |menu|
|
|
78
|
+
menu.with_trigger(variant: :outline) { "فتح" }
|
|
79
|
+
menu.with_item { "الملف الشخصي" }
|
|
80
|
+
menu.with_sub do |sub|
|
|
81
|
+
sub.with_trigger { "المزيد" }
|
|
82
|
+
sub.with_item { "استيراد" }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Server-rendered open state (controllable-state: the attributes
|
|
88
|
+
# are the store; the controller reconciles on connect).
|
|
89
|
+
def open
|
|
90
|
+
render_component(open: true, align: :start) do |menu|
|
|
91
|
+
menu.with_trigger(variant: :outline) { "Open" }
|
|
92
|
+
menu.with_label { "My Account" }
|
|
93
|
+
menu.with_item { "Profile" }
|
|
94
|
+
menu.with_separator
|
|
95
|
+
menu.with_item(variant: :destructive) { "Delete" }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|