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,639 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# A single-select dropdown field: a combobox trigger opening a popup listbox.
|
|
6
|
+
module Select
|
|
7
|
+
# The closed vocabulary for the trigger size axis.
|
|
8
|
+
SIZES = %i[sm default].freeze
|
|
9
|
+
# The closed vocabulary for the popup placement-side axis.
|
|
10
|
+
SIDES = %i[top right bottom left].freeze
|
|
11
|
+
# The closed vocabulary for the popup alignment axis.
|
|
12
|
+
ALIGNS = %i[start center end].freeze
|
|
13
|
+
# The closed vocabulary for the text-direction axis.
|
|
14
|
+
DIRS = %i[ltr rtl].freeze
|
|
15
|
+
|
|
16
|
+
# The server-side option registry: every rendered option (DOM order)
|
|
17
|
+
# lands here so the hidden native <select> and the trigger's value
|
|
18
|
+
# display are rendered from the same truth as the listbox items.
|
|
19
|
+
# Duplicate values raise at render.
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
class OptionSet
|
|
23
|
+
# One registered option: committable value, plain-text label, disabled flag.
|
|
24
|
+
Entry = Struct.new(:value, :label, :disabled)
|
|
25
|
+
|
|
26
|
+
attr_reader :entries
|
|
27
|
+
|
|
28
|
+
def initialize
|
|
29
|
+
@entries = []
|
|
30
|
+
@seen = Set.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add(value:, label:, disabled: false)
|
|
34
|
+
key = value.to_s
|
|
35
|
+
raise ArgumentError, "Select item requires a non-blank value:" if key.blank?
|
|
36
|
+
unless @seen.add?(key)
|
|
37
|
+
raise ArgumentError, "duplicate Select option value #{key.inspect} - option values must be " \
|
|
38
|
+
"unique within their select"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@entries << Entry.new(key, label, disabled)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def label_for(value)
|
|
45
|
+
entries.find { |entry| entry.value == value }&.label
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Shared part builders for the option union - mixed into the root
|
|
50
|
+
# Component and the nested Group so both levels render the same
|
|
51
|
+
# anatomy. Hosts must expose #option_set (the shared OptionSet) and
|
|
52
|
+
# #selected_value.
|
|
53
|
+
#
|
|
54
|
+
# @api private
|
|
55
|
+
module Helpers
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Hosts must expose #item_wiring (the root Component computes it
|
|
59
|
+
# from its :item declaration; Group receives it at construction).
|
|
60
|
+
def item_component(**)
|
|
61
|
+
Item.new(option_set: option_set, selected_value: selected_value,
|
|
62
|
+
item_wiring: item_wiring, **)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def separator_part(**options)
|
|
66
|
+
attrs = {
|
|
67
|
+
"data-slot" => "select-separator", "aria-hidden" => "true",
|
|
68
|
+
"class" => Style.css(:separator, class: options.delete(:class))
|
|
69
|
+
}
|
|
70
|
+
content_tag(:div, nil, attrs.merge(options))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Server-rendered always - the parent item's data-selected absence
|
|
74
|
+
# hides the check, so it stays decorative.
|
|
75
|
+
def item_indicator
|
|
76
|
+
content_tag(:span, "data-slot" => "select-item-indicator",
|
|
77
|
+
"class" => Style.css(:item_indicator, class: Style.css(:item_indicator_state))) do
|
|
78
|
+
render(Icon::Component.new(name: :check, class: Style.css(:indicator_check)))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# One role=option div. A part component on purpose (not an eager
|
|
84
|
+
# lambda): rendering happens in DOM order inside the viewport, so the
|
|
85
|
+
# shared OptionSet registers options in exactly the order the native
|
|
86
|
+
# <select> must mirror - whether the item sits at the top level or
|
|
87
|
+
# inside a group. aria-selected and data-selected are written
|
|
88
|
+
# together, never separately (unselected = data-selected absent);
|
|
89
|
+
# disabled divs carry aria-disabled + data-disabled together.
|
|
90
|
+
#
|
|
91
|
+
# @api private
|
|
92
|
+
class Item < Poetry::Core::Component
|
|
93
|
+
internal_component!
|
|
94
|
+
include Helpers
|
|
95
|
+
|
|
96
|
+
attr_reader :option_set, :selected_value
|
|
97
|
+
|
|
98
|
+
def initialize(option_set:, selected_value:, value:, item_wiring: {}, **options)
|
|
99
|
+
super(options)
|
|
100
|
+
@item_wiring = item_wiring
|
|
101
|
+
@option_set = option_set
|
|
102
|
+
@selected_value = selected_value
|
|
103
|
+
@value = value.to_s
|
|
104
|
+
@disabled = options.delete(:disabled) || false
|
|
105
|
+
@text_value = options.delete(:text_value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def call
|
|
109
|
+
label_html = content || "".html_safe
|
|
110
|
+
plain_label = (@text_value.presence || ActionView::Base.full_sanitizer.sanitize(label_html.to_s)).squish
|
|
111
|
+
option_set.add(value: @value, label: plain_label, disabled: @disabled)
|
|
112
|
+
|
|
113
|
+
selected = selected_value.present? && @value == selected_value
|
|
114
|
+
attrs = {
|
|
115
|
+
"data-slot" => "select-item", "role" => "option", "tabindex" => "-1",
|
|
116
|
+
"data-poetry-collection-item" => "", "data-value" => @value,
|
|
117
|
+
"aria-selected" => selected.to_s,
|
|
118
|
+
"class" => Style.css(:item, class: html_attributes.delete(:class))
|
|
119
|
+
}.merge(@item_wiring)
|
|
120
|
+
# Selected state: bare data-selected on the committed option,
|
|
121
|
+
# nothing while unselected (absence IS the state).
|
|
122
|
+
attrs["data-selected"] = "" if selected
|
|
123
|
+
if @disabled
|
|
124
|
+
attrs["aria-disabled"] = "true"
|
|
125
|
+
attrs["data-disabled"] = ""
|
|
126
|
+
end
|
|
127
|
+
attrs["data-text-value"] = @text_value if @text_value
|
|
128
|
+
merged = Poetry::Core::HTML::Attributes.merged(attrs, html_attributes)
|
|
129
|
+
# data-value is RESERVED (the native-<select> mirror matches
|
|
130
|
+
# options by it) - the value: argument beats any caller spelling.
|
|
131
|
+
merged["data-value"] = @value
|
|
132
|
+
content_tag(:div, merged) do
|
|
133
|
+
safe_join([item_indicator,
|
|
134
|
+
content_tag(:span, label_html, "data-slot" => "select-item-text",
|
|
135
|
+
"class" => "cn-select-item-text shrink-0 whitespace-nowrap")])
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# A single-select dropdown: a button showing the committed value that
|
|
141
|
+
# opens a popup listbox of options, with arrow-key navigation and
|
|
142
|
+
# typeahead. Reach for it when a form field commits exactly one value
|
|
143
|
+
# from a short, known list (long or filterable lists belong to
|
|
144
|
+
# Combobox; 2-4 options read better as a RadioGroup).
|
|
145
|
+
#
|
|
146
|
+
# Form participation: a visually-hidden native <select> with real
|
|
147
|
+
# server-rendered <option>s is the serialization truth - the initial
|
|
148
|
+
# value posts before any JS runs, required: rides native constraint
|
|
149
|
+
# validation, browser autofill lands on the native select and is
|
|
150
|
+
# adopted into the UI, and committing an option dispatches native
|
|
151
|
+
# change/input events so auto-submitting forms work unmodified. The
|
|
152
|
+
# popup drops below the trigger by default. Every Select needs an
|
|
153
|
+
# accessible name (a Field label via id:, or aria-label) or it
|
|
154
|
+
# raises at render.
|
|
155
|
+
#
|
|
156
|
+
# @example A named form select
|
|
157
|
+
# render Poetry::Ui::Select::Component.new(name: "fruit", id: "fruit",
|
|
158
|
+
# placeholder: "Pick a fruit") do |select|
|
|
159
|
+
# select.with_item(value: "apple") { "Apple" }
|
|
160
|
+
# select.with_item(value: "banana") { "Banana" }
|
|
161
|
+
# end
|
|
162
|
+
class Component < Poetry::Core::Component
|
|
163
|
+
include Helpers
|
|
164
|
+
|
|
165
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
166
|
+
AGENT_RULES = [
|
|
167
|
+
"Use poetry_select (f.poetry_select in forms) - never hand-roll role=listbox popups, and never " \
|
|
168
|
+
"fake a select with DropdownMenu radio items bound to a hidden field.",
|
|
169
|
+
"Options are VALUES. If activating an option should DO something beyond setting a value, it's a " \
|
|
170
|
+
"DropdownMenu item.",
|
|
171
|
+
"In forms, ALWAYS go through f.poetry_select - it wires name/id/value/errors/required; bare " \
|
|
172
|
+
"poetry_select in a form is a smell.",
|
|
173
|
+
"Every Select MUST be named: a Field label (id: + label[for]) or aria-label. A bare unnamed " \
|
|
174
|
+
"select fails at render - do not suppress it.",
|
|
175
|
+
"NEVER write aria-selected without its data-selected twin, and NEVER write the display text without " \
|
|
176
|
+
"writing the native select's value first - the controller does all three; agents patching DOM must too.",
|
|
177
|
+
"Do not put interactive elements inside options (an option IS the interactive unit).",
|
|
178
|
+
"Long/filterable/async lists or multi-select -> Combobox, not a 50-option Select; 2-4 options -> " \
|
|
179
|
+
"RadioGroup.",
|
|
180
|
+
"The hidden native select is plumbing - never target it with styles, labels, or Capybara " \
|
|
181
|
+
"selectors (drive the combobox like a user).",
|
|
182
|
+
"Positioning is popper-only: poetry Select drops below the trigger (the item-aligned overlay " \
|
|
183
|
+
"mode is not ported - a documented parity delta)."
|
|
184
|
+
].freeze
|
|
185
|
+
|
|
186
|
+
# The trigger-bound ARIA surface: Field's control_attributes (and
|
|
187
|
+
# bare aria-label usage) land on the TRIGGER - the combobox is the
|
|
188
|
+
# interactive control the label must reach - never on the root div.
|
|
189
|
+
TRIGGER_ARIA_KEYS = %w[label labelledby describedby invalid required].freeze
|
|
190
|
+
|
|
191
|
+
# The same facts the before_render raise enforces, stated statically
|
|
192
|
+
# so static checks can flag a missing item without rendering.
|
|
193
|
+
REQUIRED_SLOTS = { item: "at least one item" }.freeze
|
|
194
|
+
|
|
195
|
+
renders_one :trigger, doc: "Optional custom trigger content rendered BEFORE the value span (rare); the " \
|
|
196
|
+
"component owns role=combobox + the aria wiring + the chevron regardless, so " \
|
|
197
|
+
"composition cannot drop the contract."
|
|
198
|
+
|
|
199
|
+
renders_many :items,
|
|
200
|
+
doc: "The option UNION: item | group (label + items) | separator - one ordered collection " \
|
|
201
|
+
"(interleaving preserved; items and groups are part COMPONENTS so option registration " \
|
|
202
|
+
"follows render/DOM order). Scroll buttons, the viewport, and the native select are " \
|
|
203
|
+
"component-owned anatomy, never caller-placed.",
|
|
204
|
+
types: {
|
|
205
|
+
item: { renders: ->(**options) { item_component(**options) }, as: :item },
|
|
206
|
+
group: {
|
|
207
|
+
renders: lambda { |**options|
|
|
208
|
+
Group.new(option_set: option_set, selected_value: selected_value,
|
|
209
|
+
item_wiring: item_wiring, **options)
|
|
210
|
+
},
|
|
211
|
+
as: :group
|
|
212
|
+
},
|
|
213
|
+
separator: { renders: ->(**options) { separator_part(**options) }, as: :separator }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
use_stimulus do
|
|
217
|
+
on :root do
|
|
218
|
+
controller :select do
|
|
219
|
+
register
|
|
220
|
+
value :open
|
|
221
|
+
value :value, from: :value_string
|
|
222
|
+
value :modal
|
|
223
|
+
value :loop
|
|
224
|
+
value :align_item_with_trigger
|
|
225
|
+
end
|
|
226
|
+
controller :popper do
|
|
227
|
+
register
|
|
228
|
+
value :side
|
|
229
|
+
value :align
|
|
230
|
+
value :side_offset
|
|
231
|
+
value :align_offset
|
|
232
|
+
value :avoid_collisions
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
on :trigger do
|
|
236
|
+
controller :select do
|
|
237
|
+
action :toggle, on: :click
|
|
238
|
+
action :trigger_keydown, on: :keydown
|
|
239
|
+
end
|
|
240
|
+
controller(:popper) { target :anchor }
|
|
241
|
+
end
|
|
242
|
+
on :content do
|
|
243
|
+
controller(:popper) { target :content }
|
|
244
|
+
end
|
|
245
|
+
# Every option row (root-level and grouped) - the anatomy
|
|
246
|
+
# classes receive it as item_wiring at construction.
|
|
247
|
+
on :item do
|
|
248
|
+
controller(:select) { action :commit, on: :click }
|
|
249
|
+
end
|
|
250
|
+
on :native do
|
|
251
|
+
controller(:select) { action :native_changed, on: :change }
|
|
252
|
+
end
|
|
253
|
+
on :viewport do
|
|
254
|
+
controller(:select) { action :sync_scroll_buttons, on: :scroll }
|
|
255
|
+
end
|
|
256
|
+
on :scroll_button do
|
|
257
|
+
controller :select do
|
|
258
|
+
action :scroll_hold_start, on: :pointerenter
|
|
259
|
+
action :scroll_hold_stop, on: :pointerleave
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
option :value, :string,
|
|
265
|
+
doc: "The committed option value - the item whose value: matches renders as selected and its label " \
|
|
266
|
+
"fills the trigger."
|
|
267
|
+
option :name, :string, doc: "The form field name, carried by the hidden native <select>."
|
|
268
|
+
option :placeholder, :string,
|
|
269
|
+
doc: "Text shown in the trigger until an option is committed; also rendered as the blank native " \
|
|
270
|
+
"<option> (posts \"\" when untouched)."
|
|
271
|
+
option :id, :string,
|
|
272
|
+
doc: "The trigger's DOM id - what a Field label's for: must point at; giving one also satisfies the " \
|
|
273
|
+
"accessible-name requirement."
|
|
274
|
+
option :open, :boolean, default: false, doc: "Server-renders the popup already open."
|
|
275
|
+
option :required, :boolean, default: false,
|
|
276
|
+
doc: "Marks the hidden native <select> required - native constraint validation " \
|
|
277
|
+
"blocks submission while unset."
|
|
278
|
+
option :disabled, :boolean, default: false, doc: "Disables the trigger and the hidden native <select>."
|
|
279
|
+
option :modal, :boolean, default: true, doc: "While open, blocks pointer interaction outside the popup."
|
|
280
|
+
option :side, :symbol, default: :bottom, doc: "Preferred popup side relative to the trigger."
|
|
281
|
+
option :align, :symbol, default: :start, doc: "Popup alignment along the chosen side's edge."
|
|
282
|
+
option :side_offset, :integer, default: 4, doc: "Gap in pixels between trigger and popup."
|
|
283
|
+
option :align_offset, :integer, default: 0, doc: "Pixel shift along the alignment axis."
|
|
284
|
+
option :avoid_collisions, :boolean, default: true, doc: "Flips/shifts the popup to keep it inside the viewport."
|
|
285
|
+
option :loop, :boolean, default: false,
|
|
286
|
+
doc: "Arrow-key navigation wraps from the last option back to the first."
|
|
287
|
+
option :align_item_with_trigger, :boolean, default: false,
|
|
288
|
+
doc: "Opens the popup OVER the trigger with the selected item " \
|
|
289
|
+
"aligned on it (native-select feel); falls back to regular " \
|
|
290
|
+
"below-the-trigger positioning on touch, viewport-edge " \
|
|
291
|
+
"triggers, or squeezed heights."
|
|
292
|
+
option :dir, :symbol, doc: "Text direction for the select and its popup."
|
|
293
|
+
option :size, :symbol, default: :default, doc: "The trigger size axis."
|
|
294
|
+
option :trigger_class, :string,
|
|
295
|
+
doc: "Extra classes merged onto the trigger button (e.g. w-full over the base w-fit); class: styles " \
|
|
296
|
+
"the root wrapper instead."
|
|
297
|
+
|
|
298
|
+
validates :size, inclusion: { in: SIZES }
|
|
299
|
+
validates :side, inclusion: { in: SIDES }
|
|
300
|
+
validates :align, inclusion: { in: ALIGNS }
|
|
301
|
+
validates :dir, inclusion: { in: DIRS }, allow_nil: true
|
|
302
|
+
|
|
303
|
+
part "select", "Root wrapper carrying both controllers (select + popper) and the " \
|
|
304
|
+
"optional dir attribute"
|
|
305
|
+
part "select-native", "The visually-hidden native <select> - the serialization truth " \
|
|
306
|
+
"(name/required/disabled + every option); plumbing, never styled or targeted"
|
|
307
|
+
part "select-trigger", "The role=combobox button the field label reaches - the value " \
|
|
308
|
+
"display and chevron ride inside",
|
|
309
|
+
states: {
|
|
310
|
+
"data-size" => { condition: "always - the resolved size variant",
|
|
311
|
+
values: SIZES.map(&:to_s) },
|
|
312
|
+
"data-placeholder" => "no option is committed (bare; the controller toggles it " \
|
|
313
|
+
"on every commit)",
|
|
314
|
+
"data-popup-open" => "the popup is open (bare while open, absent while closed - " \
|
|
315
|
+
"the controller flips it with the open state)"
|
|
316
|
+
}
|
|
317
|
+
part "select-value", "The value display span - the selected option's label, or the placeholder",
|
|
318
|
+
states: {
|
|
319
|
+
"data-placeholder" => "placeholder: is given - carries the placeholder text so a " \
|
|
320
|
+
"later clear can restore it"
|
|
321
|
+
}
|
|
322
|
+
part "select-content", "The popper-positioned popup shell (scroll buttons + viewport) - " \
|
|
323
|
+
"open/closed and the resolved placement ride here",
|
|
324
|
+
states: {
|
|
325
|
+
"data-open" => "popup is open (the controller flips the pair at runtime)",
|
|
326
|
+
"data-closed" => "popup is closed or animating out (the server-rendered state)",
|
|
327
|
+
"data-side" => { condition: "the placement side - server-rendered from side:, " \
|
|
328
|
+
"rewritten to the resolved side by popper on open",
|
|
329
|
+
values: SIDES.map(&:to_s) },
|
|
330
|
+
"data-align" => { condition: "the placement alignment - server-rendered from " \
|
|
331
|
+
"align:, rewritten by popper on open",
|
|
332
|
+
values: ALIGNS.map(&:to_s) }
|
|
333
|
+
},
|
|
334
|
+
vars: {
|
|
335
|
+
"--transform-origin" => "popper - the animation origin matching the resolved placement",
|
|
336
|
+
"--available-width" => "popper - viewport space available to the popup post-flip",
|
|
337
|
+
"--available-height" => "popper - viewport space available to the popup post-flip",
|
|
338
|
+
"--anchor-width" => "popper - the trigger's measured width",
|
|
339
|
+
"--anchor-height" => "popper - the trigger's measured height",
|
|
340
|
+
"--radix-select-trigger-width" => "the select controller measures the trigger on " \
|
|
341
|
+
"open - the viewport's min-width binding",
|
|
342
|
+
"--radix-select-trigger-height" => "the select controller measures the trigger on " \
|
|
343
|
+
"open - the viewport's MINIMUM-height binding " \
|
|
344
|
+
"(a hard height collapses the popup to the " \
|
|
345
|
+
"trigger; the list grows past it)"
|
|
346
|
+
}
|
|
347
|
+
part "select-scroll-up-button", "Hover-scroll affordance above the viewport - rendered " \
|
|
348
|
+
"always but hidden; the controller unhides it per scroll " \
|
|
349
|
+
"extremes (aria-hidden)"
|
|
350
|
+
part "select-scroll-down-button", "Hover-scroll affordance below the viewport (the same " \
|
|
351
|
+
"contract as the up button)"
|
|
352
|
+
part "select-viewport", "The role=listbox scroll container - the options' actual parent, " \
|
|
353
|
+
"labelled from the trigger"
|
|
354
|
+
part "select-group", "role=group wrapper labelled by its select-label heading"
|
|
355
|
+
part "select-label", "The group heading - styled, no ARIA role (the group points at it " \
|
|
356
|
+
"via aria-labelledby)"
|
|
357
|
+
part "select-item", "One role=option div - selection, disablement, and the committable " \
|
|
358
|
+
"value ride here",
|
|
359
|
+
states: {
|
|
360
|
+
"data-value" => "always - the option's committable value (the native <option> twin)",
|
|
361
|
+
"data-selected" => "the option is committed (bare; absent while unselected - the " \
|
|
362
|
+
"controller twin-writes it with aria-selected)",
|
|
363
|
+
"data-disabled" => "disabled: is set (aria-disabled rides along)"
|
|
364
|
+
}
|
|
365
|
+
part "select-item-indicator", "The check gutter - server-rendered always; the parent " \
|
|
366
|
+
"item's data-selected absence hides it"
|
|
367
|
+
part "select-item-text", "The option's label span - the value display copies from it"
|
|
368
|
+
part "select-separator", "Decorative divider between options (aria-hidden)"
|
|
369
|
+
|
|
370
|
+
# @api private
|
|
371
|
+
def initialize(attributes = {})
|
|
372
|
+
if attributes.key?(:multiple) || attributes.key?("multiple")
|
|
373
|
+
raise ArgumentError, "Select does not support multiple: - multi-select is Combobox territory"
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
super
|
|
377
|
+
@trigger_aria = extract_trigger_aria!
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# @api private
|
|
381
|
+
def before_render
|
|
382
|
+
raise ArgumentError, "Select requires at least one item (with_item / with_group)" unless items?
|
|
383
|
+
|
|
384
|
+
return if named?
|
|
385
|
+
|
|
386
|
+
raise ArgumentError, "Select requires an accessible name - compose with a Field label " \
|
|
387
|
+
"(id: + label[for: id]) or pass 'aria-label'"
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# The Field-targetable id lands on the TRIGGER (label[for=id]
|
|
391
|
+
# click-focuses the combobox); content/native ids derive from it.
|
|
392
|
+
# @api private
|
|
393
|
+
def trigger_id
|
|
394
|
+
@trigger_id ||= id.presence || poetry_instance_id("poetry-select")
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# @api private
|
|
398
|
+
def content_id
|
|
399
|
+
"#{trigger_id}-content"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# @api private
|
|
403
|
+
def native_id
|
|
404
|
+
"#{trigger_id}-native"
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# @api private
|
|
408
|
+
def option_set
|
|
409
|
+
@option_set ||= OptionSet.new
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# @api private
|
|
413
|
+
def selected_value
|
|
414
|
+
@selected_value ||= value.presence.to_s
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# @api private
|
|
418
|
+
def selected_label
|
|
419
|
+
option_set.label_for(selected_value) if selected_value.present?
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# @api private
|
|
423
|
+
def root_attributes
|
|
424
|
+
root = { "data-slot" => "select" }
|
|
425
|
+
root["dir"] = dir.to_s if dir
|
|
426
|
+
html_attributes.merge_if_not_set(
|
|
427
|
+
root.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
|
|
428
|
+
)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# The serialization truth: a real <select> carrying name/required/
|
|
432
|
+
# disabled and ALL options with selected - visually hidden (sr-only,
|
|
433
|
+
# painted) and out of both trees (aria-hidden + tabindex=-1). Its
|
|
434
|
+
# change event is the autofill-adoption path.
|
|
435
|
+
# @api private
|
|
436
|
+
def native_select
|
|
437
|
+
attrs = {
|
|
438
|
+
"id" => native_id, "data-slot" => "select-native",
|
|
439
|
+
"aria-hidden" => "true", "tabindex" => "-1", "class" => Style.css(:native)
|
|
440
|
+
}
|
|
441
|
+
attrs["name"] = name if name.present?
|
|
442
|
+
attrs["required"] = true if required
|
|
443
|
+
attrs["disabled"] = true if disabled
|
|
444
|
+
attrs.merge!(stimulus_attributes_for(:native))
|
|
445
|
+
content_tag(:select, native_options, attrs)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# @api private
|
|
449
|
+
def trigger_button
|
|
450
|
+
attrs = {
|
|
451
|
+
"id" => trigger_id, "data-slot" => "select-trigger", "type" => "button",
|
|
452
|
+
"role" => "combobox", "aria-expanded" => open.to_s, "aria-controls" => content_id,
|
|
453
|
+
"aria-autocomplete" => "none", "data-size" => size.to_s,
|
|
454
|
+
"class" => css(:trigger, class: trigger_class)
|
|
455
|
+
}
|
|
456
|
+
# Trigger open state: bare data-popup-open while open, no
|
|
457
|
+
# attribute while closed (absence IS the state).
|
|
458
|
+
attrs["data-popup-open"] = "" if open
|
|
459
|
+
attrs["data-placeholder"] = "" unless selected_label
|
|
460
|
+
attrs["disabled"] = true if disabled
|
|
461
|
+
attrs.merge!(stimulus_attributes_for(:trigger))
|
|
462
|
+
attrs.merge!(trigger_aria_attributes)
|
|
463
|
+
content_tag(:button, attrs) do
|
|
464
|
+
safe_join([trigger, value_display, chevron].compact)
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# @api private
|
|
469
|
+
def content_attributes
|
|
470
|
+
# No widget role here: the popup shell holds scroll buttons too,
|
|
471
|
+
# and role=listbox permits only option/group children (axe
|
|
472
|
+
# aria-required-children) - the role lives on the
|
|
473
|
+
# viewport, the options' actual parent.
|
|
474
|
+
attrs = {
|
|
475
|
+
"id" => content_id,
|
|
476
|
+
"tabindex" => "-1", "data-slot" => "select-content", (open ? "data-open" : "data-closed") => "",
|
|
477
|
+
# Initial placement, re-resolved live by popper on open.
|
|
478
|
+
"data-side" => side, "data-align" => align,
|
|
479
|
+
"class" => css(:content)
|
|
480
|
+
}.merge(stimulus_attributes_for(:content))
|
|
481
|
+
attrs["hidden"] = true unless open
|
|
482
|
+
attrs
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# @api private
|
|
486
|
+
def viewport_attributes
|
|
487
|
+
{
|
|
488
|
+
"data-slot" => "select-viewport", "role" => "listbox",
|
|
489
|
+
"aria-labelledby" => @trigger_aria["labelledby"].presence || trigger_id,
|
|
490
|
+
"class" => css(:viewport)
|
|
491
|
+
}.merge(stimulus_attributes_for(:viewport))
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Scroll affordances render ALWAYS but hidden - the controller owns
|
|
495
|
+
# visibility per scroll extremes (syncScrollButtons) and runs the
|
|
496
|
+
# rAF hover-scroll; aria-hidden throughout (keyboard scrolls via
|
|
497
|
+
# focus + scroll-my-1).
|
|
498
|
+
# @api private
|
|
499
|
+
def scroll_button(direction)
|
|
500
|
+
attrs = {
|
|
501
|
+
"data-slot" => "select-scroll-#{direction}-button", "aria-hidden" => "true",
|
|
502
|
+
"hidden" => true,
|
|
503
|
+
"class" => Style.css(:scroll_button, class: "cn-select-scroll-#{direction}-button")
|
|
504
|
+
}.merge(stimulus_attributes_for(:scroll_button))
|
|
505
|
+
content_tag(:div, attrs) do
|
|
506
|
+
render(Icon::Component.new(name: :"chevron-#{direction}", class: Style.css(:scroll_icon)))
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
private
|
|
511
|
+
|
|
512
|
+
def named?
|
|
513
|
+
id.present? || @trigger_aria["label"].present? || @trigger_aria["labelledby"].present?
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Pull the trigger-bound aria-* out of the root's html attributes
|
|
517
|
+
# (both flat "aria-label" and nested aria: {label:} spellings) so
|
|
518
|
+
# Field's control_attributes wire the combobox, not the wrapper.
|
|
519
|
+
def extract_trigger_aria!
|
|
520
|
+
aria = {}.with_indifferent_access
|
|
521
|
+
nested = @html_attributes.delete("aria")
|
|
522
|
+
nested.each { |nested_key, nested_value| aria[nested_key] = nested_value } if nested.is_a?(Hash)
|
|
523
|
+
@html_attributes.keys.grep(/\Aaria-/).each do |flat|
|
|
524
|
+
aria[flat.delete_prefix("aria-")] = @html_attributes.delete(flat)
|
|
525
|
+
end
|
|
526
|
+
aria
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def trigger_aria_attributes
|
|
530
|
+
TRIGGER_ARIA_KEYS.each_with_object({}) do |key, attrs|
|
|
531
|
+
attrs["aria-#{key}"] = @trigger_aria[key] unless @trigger_aria[key].nil?
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# The value DISPLAY: server-rendered selected-option label (no
|
|
536
|
+
# FOUC), or the placeholder text; data-placeholder on the span
|
|
537
|
+
# carries the placeholder so a later clear can restore it.
|
|
538
|
+
def value_display
|
|
539
|
+
attrs = { "data-slot" => "select-value", "class" => "cn-select-value" }
|
|
540
|
+
attrs["data-placeholder"] = placeholder if placeholder.present?
|
|
541
|
+
content_tag(:span, selected_label || placeholder, attrs)
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def chevron
|
|
545
|
+
render(Icon::Component.new(name: :"chevron-down", class: Style.css(:trigger_icon)))
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# The blank option (Rails include_blank semantics, value="") rides
|
|
549
|
+
# the placeholder - and is always present when no option matches,
|
|
550
|
+
# so the native select never silently rests on the first option
|
|
551
|
+
# while the trigger shows the placeholder.
|
|
552
|
+
def native_options
|
|
553
|
+
rendered = []
|
|
554
|
+
if placeholder.present? || selected_label.nil?
|
|
555
|
+
rendered << tag.option(placeholder.to_s, value: "", selected: selected_label.nil? || nil)
|
|
556
|
+
end
|
|
557
|
+
option_set.entries.each do |entry|
|
|
558
|
+
rendered << tag.option(entry.label, value: entry.value,
|
|
559
|
+
selected: entry.value == selected_value || nil,
|
|
560
|
+
disabled: entry.disabled || nil)
|
|
561
|
+
end
|
|
562
|
+
safe_join(rendered)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# BOTH controllers build into ONE Attributes instance - a plain
|
|
566
|
+
# Hash#merge of two would overwrite data-controller instead of
|
|
567
|
+
# token-concatenating it.
|
|
568
|
+
def value_string = value.to_s
|
|
569
|
+
|
|
570
|
+
def item_wiring
|
|
571
|
+
@item_wiring ||= stimulus_attributes_for(:item)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
private :trigger_id, :content_id, :native_id, :option_set, :selected_value, :selected_label, :root_attributes
|
|
575
|
+
private :native_select, :trigger_button, :content_attributes, :viewport_attributes, :scroll_button
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# role=group with an optional heading label (aria-labelledby wired) -
|
|
579
|
+
# the same item union one level down, registering its options into
|
|
580
|
+
# the PARENT's option set so the native select and value display see
|
|
581
|
+
# every option.
|
|
582
|
+
#
|
|
583
|
+
# @api private
|
|
584
|
+
class Group < Poetry::Core::Component
|
|
585
|
+
internal_component!
|
|
586
|
+
include Helpers
|
|
587
|
+
|
|
588
|
+
attr_reader :option_set, :selected_value, :item_wiring
|
|
589
|
+
|
|
590
|
+
renders_many :items,
|
|
591
|
+
doc: "The same item | separator union as the root, one level down.",
|
|
592
|
+
types: {
|
|
593
|
+
item: { renders: ->(**options) { item_component(**options) }, as: :item },
|
|
594
|
+
separator: { renders: ->(**options) { separator_part(**options) }, as: :separator }
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
# heading: is the canonical keyword; label: is accepted as an alias.
|
|
598
|
+
def initialize(option_set:, selected_value:, heading: nil, label: nil, item_wiring: {}, **extra_attributes)
|
|
599
|
+
super(extra_attributes)
|
|
600
|
+
@item_wiring = item_wiring
|
|
601
|
+
@option_set = option_set
|
|
602
|
+
@selected_value = selected_value
|
|
603
|
+
@label_text = heading || label
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def before_render
|
|
607
|
+
raise ArgumentError, "Select group requires at least one item" unless items?
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
def call
|
|
611
|
+
attrs = { "data-slot" => "select-group", "role" => "group", "class" => "cn-select-group" }
|
|
612
|
+
attrs["aria-labelledby"] = label_id if @label_text
|
|
613
|
+
content_tag(:div, Poetry::Core::HTML::Attributes.merged(attrs, html_attributes)) do
|
|
614
|
+
safe_join([label_part, *items].compact)
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
private
|
|
619
|
+
|
|
620
|
+
def group_id
|
|
621
|
+
@group_id ||= poetry_instance_id("poetry-select-group")
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def label_id
|
|
625
|
+
"#{group_id}-label"
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# A styled heading, no ARIA role - the group points at it via
|
|
629
|
+
# aria-labelledby.
|
|
630
|
+
def label_part
|
|
631
|
+
return if @label_text.blank?
|
|
632
|
+
|
|
633
|
+
content_tag(:div, @label_text, "data-slot" => "select-label", "id" => label_id,
|
|
634
|
+
"class" => Style.css(:label))
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
end
|