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,323 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# A mutually exclusive option set: radio dots or selectable choice cards.
|
|
6
|
+
module RadioGroup
|
|
7
|
+
# A set of mutually exclusive options, exactly one checkable -
|
|
8
|
+
# radio dots with labels, or selectable choice cards (item
|
|
9
|
+
# variant: :card). Reach for it when a handful of options should all
|
|
10
|
+
# be visible at once; larger lists belong in a Select.
|
|
11
|
+
#
|
|
12
|
+
# Keyboard: the group is one Tab stop; arrow keys move between items
|
|
13
|
+
# and selection follows that navigation - Tab into the group never
|
|
14
|
+
# changes the value. Form participation: one hidden native
|
|
15
|
+
# <input type=radio> per item under the shared name:, so the checked
|
|
16
|
+
# value serializes exactly like collection_radio_buttons and NOTHING
|
|
17
|
+
# submits while none is checked (presence validation stays honest).
|
|
18
|
+
# The server renders the roving tab stop (checked item tabindex=0,
|
|
19
|
+
# rest -1) so the one-Tab-stop contract holds before JS connects.
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# render Poetry::Ui::RadioGroup::Component.new(
|
|
23
|
+
# name: "plan", value: "monthly", label: "Billing plan"
|
|
24
|
+
# ) do |group|
|
|
25
|
+
# group.with_item(value: "monthly", label: "Monthly")
|
|
26
|
+
# group.with_item(value: "yearly", label: "Yearly")
|
|
27
|
+
# end
|
|
28
|
+
class Component < Poetry::Core::Component
|
|
29
|
+
# The closed vocabulary for the keyboard-axis option.
|
|
30
|
+
ORIENTATIONS = %i[both vertical horizontal].freeze
|
|
31
|
+
|
|
32
|
+
# The closed vocabulary for the per-item variant axis.
|
|
33
|
+
ITEM_VARIANTS = %i[default card].freeze
|
|
34
|
+
|
|
35
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
36
|
+
AGENT_RULES = [
|
|
37
|
+
"Use poetry_radio_group / form.radio_group - never hand-roll role=radio buttons.",
|
|
38
|
+
"Every item MUST have a unique value: (ArgumentError on duplicates).",
|
|
39
|
+
"The GROUP must be labelled - label: (or aria-labelledby) - an unlabelled radiogroup is an " \
|
|
40
|
+
"APG violation (ArgumentError).",
|
|
41
|
+
"Pair every item with a visible label (item label: renders the Label for= pairing) - a bare " \
|
|
42
|
+
"dot is not an option.",
|
|
43
|
+
"variant: :card renders the choice-card row (title + description: inside a selectable " \
|
|
44
|
+
"bordered label) - the pick-a-plan pattern; the whole card toggles the radio.",
|
|
45
|
+
"NEVER write the checked attributes (data-checked/data-unchecked) without aria-checked (the " \
|
|
46
|
+
"controller writes both; agents patching DOM must too).",
|
|
47
|
+
"Do not use RadioGroup for navigation or immediate actions; checking must not submit or " \
|
|
48
|
+
"navigate by itself.",
|
|
49
|
+
"7+ options: use Select instead.",
|
|
50
|
+
"Wire errors through Field/FormBuilder (invalid: + describedby on the root) - never a bare " \
|
|
51
|
+
"red ring."
|
|
52
|
+
].freeze
|
|
53
|
+
|
|
54
|
+
# The same facts the before_render raise enforces, stated statically
|
|
55
|
+
# so static checks can flag a missing item without rendering.
|
|
56
|
+
REQUIRED_SLOTS = { item: "at least one radio item" }.freeze
|
|
57
|
+
|
|
58
|
+
renders_many :items,
|
|
59
|
+
doc: "One item per option: a real button[role=radio] carrying its own hidden native radio; " \
|
|
60
|
+
"label: renders the dot beside a paired Label. variant: :card renders the choice-card row " \
|
|
61
|
+
"instead - title (+ optional description:) inside a selectable bordered label, the radio " \
|
|
62
|
+
"pinned to the right.",
|
|
63
|
+
renders: lambda { |value:, label: nil, id: nil, disabled: false,
|
|
64
|
+
description: nil, variant: :default, **options|
|
|
65
|
+
unless ITEM_VARIANTS.include?(variant)
|
|
66
|
+
raise ArgumentError,
|
|
67
|
+
"unknown RadioGroup item variant #{variant.inspect} - known: #{ITEM_VARIANTS.join(", ")}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
item_value = register_item_value!(value)
|
|
71
|
+
item_id = id.presence || "#{control_id}-#{slug(item_value)}"
|
|
72
|
+
item_disabled = disabled || self.disabled
|
|
73
|
+
item = radio_item(item_value, item_id, item_disabled, options)
|
|
74
|
+
|
|
75
|
+
next card_row(item, item_id, label, description) if variant == :card
|
|
76
|
+
|
|
77
|
+
if description.present?
|
|
78
|
+
raise ArgumentError, "RadioGroup description: rides the choice-card form - pass variant: :card"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
next item if label.blank?
|
|
82
|
+
|
|
83
|
+
# The item + Label pairing row. The for= targets the BUTTON id -
|
|
84
|
+
# label clicks check via the controller (native label->button
|
|
85
|
+
# activation).
|
|
86
|
+
content_tag(:div, class: css(:row)) do
|
|
87
|
+
safe_join([item, render(Label::Component.new(for_id: item_id).with_content(label))])
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
use_stimulus do
|
|
92
|
+
on :root do
|
|
93
|
+
controller :radio_group do
|
|
94
|
+
register
|
|
95
|
+
value :value, from: :value_string, if: :value?
|
|
96
|
+
# Selection follows focus: entry fires ONLY on arrow/Home/End
|
|
97
|
+
# navigation (never Tab), so checking on entry IS the APG
|
|
98
|
+
# radio contract.
|
|
99
|
+
action :entry_check, on: event(:roving_focus, :entry)
|
|
100
|
+
end
|
|
101
|
+
controller :roving_focus do
|
|
102
|
+
register
|
|
103
|
+
# DEFAULT tabindex-managing mode: the group is ONE Tab stop.
|
|
104
|
+
value :orientation
|
|
105
|
+
value :loop
|
|
106
|
+
action :keydown, on: :keydown
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
on :item do
|
|
110
|
+
controller(:radio_group) { action :check, on: :click }
|
|
111
|
+
end
|
|
112
|
+
# THE form participant: the hidden native radio the controller
|
|
113
|
+
# syncs.
|
|
114
|
+
on :input do
|
|
115
|
+
controller(:radio_group) { target :input }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
option :name, :string, required: true,
|
|
120
|
+
doc: "The shared form name for every hidden radio (FormBuilder derives object[method])."
|
|
121
|
+
option :value, :string, doc: "The checked item's value; nil = nothing checked (pre-selection)."
|
|
122
|
+
option :required, :boolean, default: false,
|
|
123
|
+
doc: "aria-required on the ROOT only - never native required on the hidden " \
|
|
124
|
+
"inputs (constraint-validation focus would land on an aria-hidden input)."
|
|
125
|
+
option :disabled, :boolean, default: false, doc: "Disables every item (root-level)."
|
|
126
|
+
option :loop, :boolean, default: true, doc: "Arrow-key navigation wraps at the ends."
|
|
127
|
+
option :orientation, :symbol, default: :both,
|
|
128
|
+
doc: "Keyboard axis: :both allows all four arrows (the standard radio " \
|
|
129
|
+
"pattern); :vertical/:horizontal restrict the axis. No visual effect."
|
|
130
|
+
option :invalid, :boolean, default: false,
|
|
131
|
+
doc: "aria-invalid on the items (the destructive ring) - set by Field/FormBuilder " \
|
|
132
|
+
"from model errors."
|
|
133
|
+
option :label, :string,
|
|
134
|
+
doc: "The group accessible name -> aria-label (or wire aria-labelledby yourself) - REQUIRED: an " \
|
|
135
|
+
"unlabelled radiogroup fails the audit."
|
|
136
|
+
|
|
137
|
+
validates :orientation, inclusion: { in: ORIENTATIONS }
|
|
138
|
+
|
|
139
|
+
part "radio-group", "The role=radiogroup root - one Tab stop; items (and their " \
|
|
140
|
+
"label-pairing rows) render as direct children",
|
|
141
|
+
states: {
|
|
142
|
+
"data-disabled" => "disabled: is set on the root - every item disables with it"
|
|
143
|
+
}
|
|
144
|
+
part "radio-group-item", "A button[role=radio] per item - carries its own hidden " \
|
|
145
|
+
"native radio as a sibling",
|
|
146
|
+
states: {
|
|
147
|
+
"data-checked" => "the checked item (the controller writes the pair and " \
|
|
148
|
+
"aria-checked together on every item)",
|
|
149
|
+
"data-unchecked" => "every other item",
|
|
150
|
+
"data-disabled" => "the item (or the whole group) is disabled - also the " \
|
|
151
|
+
"roving-focus collection filter",
|
|
152
|
+
"data-value" => "always - the item's value (keys the checked-value machine)"
|
|
153
|
+
}
|
|
154
|
+
part "radio-group-indicator", "The theme-sized centering box holding the checked dot " \
|
|
155
|
+
"(the dot itself is the themed " \
|
|
156
|
+
".cn-radio-group-indicator-icon span) - hidden (the " \
|
|
157
|
+
"native attribute, toggled by the controller) while " \
|
|
158
|
+
"unchecked"
|
|
159
|
+
part "radio-group-card", "The choice-card row (variant: :card) - a <label> for= the " \
|
|
160
|
+
"radio button, so the whole card toggles; the checked " \
|
|
161
|
+
"treatments key on data-checked inside it"
|
|
162
|
+
part "radio-group-card-content", "Text column of a choice-card item (variant: :card) - " \
|
|
163
|
+
"title and description stack inside the card label"
|
|
164
|
+
part "radio-group-card-title", "The choice card's title line (the item label:)"
|
|
165
|
+
part "radio-group-card-description", "Muted copy under the choice card's title " \
|
|
166
|
+
"(description:)"
|
|
167
|
+
|
|
168
|
+
# @api private
|
|
169
|
+
def before_render
|
|
170
|
+
raise ArgumentError, "RadioGroup requires at least one with_item" unless items?
|
|
171
|
+
|
|
172
|
+
return if named?
|
|
173
|
+
|
|
174
|
+
raise ArgumentError,
|
|
175
|
+
"unlabelled RadioGroup - give it label: (or aria-labelledby); a radiogroup without an " \
|
|
176
|
+
"accessible name is an APG violation"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# @api private
|
|
180
|
+
def checked?(item_value)
|
|
181
|
+
value.present? && value.to_s == item_value
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# The label-for/aria target root id - server-stable (Field-issued
|
|
185
|
+
# or auto) so item ids derive deterministically.
|
|
186
|
+
# @api private
|
|
187
|
+
def control_id
|
|
188
|
+
@control_id ||= poetry_instance_id("poetry-radio-group")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @api private
|
|
192
|
+
def root_attributes
|
|
193
|
+
attrs = {
|
|
194
|
+
"role" => "radiogroup", "data-slot" => "radio-group", "id" => control_id
|
|
195
|
+
}
|
|
196
|
+
attrs["aria-required"] = true if required
|
|
197
|
+
attrs["aria-label"] = label if label.present?
|
|
198
|
+
attrs["data-disabled"] = "" if disabled
|
|
199
|
+
html_attributes.merge_if_not_set(
|
|
200
|
+
attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
def register_item_value!(value)
|
|
207
|
+
@item_values ||= Set.new
|
|
208
|
+
item_value = value.to_s
|
|
209
|
+
unless @item_values.add?(item_value)
|
|
210
|
+
raise ArgumentError, "duplicate RadioGroup item value #{item_value.inspect} - values key " \
|
|
211
|
+
"the checked-value machine"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
item_value
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# No raw user string becomes an id/selector fragment.
|
|
218
|
+
def slug(value)
|
|
219
|
+
value.gsub(/[^a-zA-Z0-9_-]+/, "-")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# The choice-card row: the whole card IS a label (for= the radio
|
|
223
|
+
# button, native label->button activation), title + description
|
|
224
|
+
# stacked left, the radio right. A plain <label> with its own
|
|
225
|
+
# radio-group-card slot, NOT the Label component - a nested
|
|
226
|
+
# data-component boundary would hand this subtree's part
|
|
227
|
+
# ownership to Label (the part-contract walker attributes DOM to
|
|
228
|
+
# the nearest component). The checked/bordered treatments ride
|
|
229
|
+
# the theme's cn-field-label rules off the item's data-checked
|
|
230
|
+
# inside.
|
|
231
|
+
def card_row(item, item_id, label, description)
|
|
232
|
+
raise ArgumentError, "RadioGroup card items need label: (the card title)" if label.blank?
|
|
233
|
+
|
|
234
|
+
text = content_tag(:div, "data-slot" => "radio-group-card-content", class: css(:card_content)) do
|
|
235
|
+
parts = [content_tag(:div, label, "data-slot" => "radio-group-card-title",
|
|
236
|
+
class: css(:card_title))]
|
|
237
|
+
if description.present?
|
|
238
|
+
parts << content_tag(:p, description, "data-slot" => "radio-group-card-description",
|
|
239
|
+
class: css(:card_description))
|
|
240
|
+
end
|
|
241
|
+
safe_join(parts)
|
|
242
|
+
end
|
|
243
|
+
content_tag(:label, safe_join([text, item]),
|
|
244
|
+
"data-slot" => "radio-group-card", "for" => item_id, class: css(:card))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def named?
|
|
248
|
+
aria = html_attributes["aria"] || {}
|
|
249
|
+
label.present? ||
|
|
250
|
+
html_attributes["aria-label"].present? || html_attributes["aria-labelledby"].present? ||
|
|
251
|
+
aria["label"].present? || aria["labelledby"].present?
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def radio_item(item_value, item_id, item_disabled, options)
|
|
255
|
+
checked = checked?(item_value)
|
|
256
|
+
|
|
257
|
+
attrs = {
|
|
258
|
+
type: "button", role: "radio", id: item_id, class: item_classes(options.delete(:class)),
|
|
259
|
+
"data-slot" => "radio-group-item", "data-poetry-collection-item" => "",
|
|
260
|
+
"data-value" => item_value,
|
|
261
|
+
"aria-checked" => checked.to_s, (checked ? "data-checked" : "data-unchecked") => "",
|
|
262
|
+
"tabindex" => tab_stop?(item_value, item_disabled) ? "0" : "-1"
|
|
263
|
+
}
|
|
264
|
+
attrs["aria-invalid"] = "true" if invalid
|
|
265
|
+
if item_disabled
|
|
266
|
+
attrs[:disabled] = true
|
|
267
|
+
attrs["data-disabled"] = "" # the roving-focus collection filter
|
|
268
|
+
end
|
|
269
|
+
attrs.merge!(stimulus_attributes_for(:item))
|
|
270
|
+
|
|
271
|
+
# The native input is the button's SIBLING - a focusable native
|
|
272
|
+
# control inside a role=radio button is an axe nested-interactive
|
|
273
|
+
# violation.
|
|
274
|
+
safe_join([
|
|
275
|
+
content_tag(:button, Poetry::Core::HTML::Attributes.merged(attrs, options)) do
|
|
276
|
+
indicator(checked)
|
|
277
|
+
end,
|
|
278
|
+
hidden_radio(item_value, item_id, checked, item_disabled)
|
|
279
|
+
])
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def indicator(checked)
|
|
283
|
+
attrs = { class: css(:indicator), "data-slot" => "radio-group-indicator" }
|
|
284
|
+
# A hidden attr toggle, not element presence - no check animation
|
|
285
|
+
# is rendered (morph-safe).
|
|
286
|
+
attrs[:hidden] = true unless checked
|
|
287
|
+
content_tag(:span, content_tag(:span, "", class: css(:dot)), attrs)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# THE form participant: native radio serialization (same form+name
|
|
291
|
+
# group), aria-hidden + tabindex=-1 (the role=radio button is the
|
|
292
|
+
# focusable/AT surface - render tests pin the no-double-Tab-stop
|
|
293
|
+
# invariant).
|
|
294
|
+
def hidden_radio(item_value, item_id, checked, item_disabled)
|
|
295
|
+
tag.input(type: "radio", name: name, value: item_value, id: "#{item_id}-input",
|
|
296
|
+
checked: checked, disabled: item_disabled, "aria-hidden": true,
|
|
297
|
+
tabindex: "-1", class: css(:input),
|
|
298
|
+
**stimulus_attributes_for(:input).transform_keys(&:to_sym))
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# The server renders the roving contract: exactly one tabindex=0 -
|
|
302
|
+
# the checked item, else the FIRST enabled item (Tab lands there
|
|
303
|
+
# when nothing is checked). Sequential across item registration.
|
|
304
|
+
def tab_stop?(item_value, item_disabled)
|
|
305
|
+
return false if @tab_stop_assigned || item_disabled
|
|
306
|
+
|
|
307
|
+
stop = value.present? ? checked?(item_value) : true
|
|
308
|
+
@tab_stop_assigned = true if stop
|
|
309
|
+
stop
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def item_classes(extra)
|
|
313
|
+
classnames(css(:item), extra)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def value_string = value.to_s
|
|
317
|
+
def value? = value.present?
|
|
318
|
+
|
|
319
|
+
private :checked?, :control_id, :root_attributes
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module RadioGroup
|
|
6
|
+
# The RadioGroup preview matrix: group configs (none-checked,
|
|
7
|
+
# one-checked, required, disabled) x item states (checked, disabled,
|
|
8
|
+
# invalid), the radio-group-demo port, and the horizontal-layout
|
|
9
|
+
# recipe (caller classes; the keyboard stays both-axis).
|
|
10
|
+
class Preview < Poetry::Core::Preview::Base
|
|
11
|
+
# @!group States
|
|
12
|
+
|
|
13
|
+
# The radio-group-demo port: value pre-checked, items paired with
|
|
14
|
+
# Labels via item label:.
|
|
15
|
+
def default
|
|
16
|
+
render_component(name: "density", value: "comfortable", label: "Density") do |group|
|
|
17
|
+
group.with_item(value: "default", label: "Default")
|
|
18
|
+
group.with_item(value: "comfortable", label: "Comfortable")
|
|
19
|
+
group.with_item(value: "compact", label: "Compact")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Nothing checked (pre-selection): Tab lands on the FIRST enabled
|
|
24
|
+
# item; nothing submits until the user chooses (presence
|
|
25
|
+
# validation stays honest).
|
|
26
|
+
def none_checked
|
|
27
|
+
render_component(name: "plan", label: "Plan") do |group|
|
|
28
|
+
group.with_item(value: "monthly", label: "Monthly")
|
|
29
|
+
group.with_item(value: "yearly", label: "Yearly")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A disabled option mid-list: skipped by Tab AND filtered from the
|
|
34
|
+
# arrow collection.
|
|
35
|
+
def disabled_item
|
|
36
|
+
render_component(name: "tier", value: "free", label: "Tier") do |group|
|
|
37
|
+
group.with_item(value: "free", label: "Free")
|
|
38
|
+
group.with_item(value: "pro", label: "Pro (unavailable)", disabled: true)
|
|
39
|
+
group.with_item(value: "enterprise", label: "Enterprise")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Root-level disabled: a locked-in choice still renders its dot
|
|
44
|
+
# (and still submits - hidden radios disable too, so it does NOT).
|
|
45
|
+
def disabled_group
|
|
46
|
+
render_component(name: "plan", value: "yearly", disabled: true, label: "Plan") do |group|
|
|
47
|
+
group.with_item(value: "monthly", label: "Monthly")
|
|
48
|
+
group.with_item(value: "yearly", label: "Yearly")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The field-level error treatment: aria-invalid on every item (the
|
|
53
|
+
# destructive ring), wired by Field/FormBuilder upstream.
|
|
54
|
+
def invalid
|
|
55
|
+
render_component(name: "plan", invalid: true, required: true, label: "Plan") do |group|
|
|
56
|
+
group.with_item(value: "monthly", label: "Monthly")
|
|
57
|
+
group.with_item(value: "yearly", label: "Yearly")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The choice-card items (variant: :card): title + description
|
|
62
|
+
# inside a selectable bordered label - the whole card toggles the
|
|
63
|
+
# radio (the upstream Choice Card recipe).
|
|
64
|
+
def choice_cards
|
|
65
|
+
render_component(name: "compute", value: "kubernetes", label: "Compute environment",
|
|
66
|
+
class: "w-80") do |group|
|
|
67
|
+
group.with_item(value: "kubernetes", label: "Kubernetes", variant: :card,
|
|
68
|
+
description: "Run GPU workloads on a K8s cluster.")
|
|
69
|
+
group.with_item(value: "vm", label: "Virtual Machine", variant: :card,
|
|
70
|
+
description: "Access a cluster to run GPU workloads.")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @!endgroup
|
|
75
|
+
|
|
76
|
+
# @!group Recipes
|
|
77
|
+
|
|
78
|
+
# Horizontal layout is a CALLER class (source has no orientation
|
|
79
|
+
# styling); the keyboard stays both-axis.
|
|
80
|
+
def horizontal_layout
|
|
81
|
+
render_component(name: "align", value: "left", label: "Alignment", class: "grid-flow-col") do |group|
|
|
82
|
+
group.with_item(value: "left", label: "Left")
|
|
83
|
+
group.with_item(value: "center", label: "Center")
|
|
84
|
+
group.with_item(value: "right", label: "Right")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Field-bound (the FormBuilder shape): the group takes the Field's
|
|
89
|
+
# id + describedby; the hint reads on the ROOT (group-level).
|
|
90
|
+
def in_a_field
|
|
91
|
+
field = Field::Component.new(
|
|
92
|
+
id: "preview-plan", label_text: "Plan",
|
|
93
|
+
hint: "You can change this anytime.", required: true
|
|
94
|
+
)
|
|
95
|
+
render_component(field) do
|
|
96
|
+
embed(Component.new(name: "plan", value: "monthly", required: true, label: "Plan",
|
|
97
|
+
id: "preview-plan",
|
|
98
|
+
"aria-describedby": "preview-plan-hint")) do |group|
|
|
99
|
+
group.with_item(value: "monthly", label: "Monthly")
|
|
100
|
+
group.with_item(value: "yearly", label: "Yearly")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @!endgroup
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module RadioGroup
|
|
6
|
+
# Style dictionary for the RadioGroup family: the well + dot
|
|
7
|
+
# treatments ride the themes. The indicator is a theme-SIZED box so
|
|
8
|
+
# the absolutely-centered dot anchors against a real rect regardless
|
|
9
|
+
# of the item's display - an unsized anchor span centers only by
|
|
10
|
+
# flow accident (7px off under nova's flex item). :input stays the
|
|
11
|
+
# hidden native form bridge and :row the label pairing row - both
|
|
12
|
+
# structural, no cn names.
|
|
13
|
+
class Style < Poetry::Core::Style
|
|
14
|
+
base "cn-radio-group w-full"
|
|
15
|
+
|
|
16
|
+
element :item, "cn-radio-group-item group/radio-group-item peer relative aspect-square " \
|
|
17
|
+
"shrink-0 border outline-none after:absolute after:-inset-x-3 " \
|
|
18
|
+
"after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
19
|
+
|
|
20
|
+
element :indicator, "cn-radio-group-indicator"
|
|
21
|
+
|
|
22
|
+
# The checked dot - a bare themed span (bg + size + centering all
|
|
23
|
+
# live in the theme's cn-radio-group-indicator-icon rule).
|
|
24
|
+
element :dot, "cn-radio-group-indicator-icon"
|
|
25
|
+
|
|
26
|
+
# The form participant: one hidden native radio PER item, shared
|
|
27
|
+
# name - out of the Tab order and the accessibility tree (the
|
|
28
|
+
# role=radio button is the accessible control).
|
|
29
|
+
element :input, "sr-only"
|
|
30
|
+
|
|
31
|
+
# The item+Label pairing row (radio-group-demo parity) rendered
|
|
32
|
+
# when an item passes label:.
|
|
33
|
+
element :row, "flex items-center gap-3"
|
|
34
|
+
|
|
35
|
+
# The choice-card row (item variant: :card): the card IS the
|
|
36
|
+
# label, wearing the Field family's cn-field-label hooks so one
|
|
37
|
+
# theme rule styles choice cards wherever they appear. Upstream
|
|
38
|
+
# nests FieldLabel > Field > FieldContent; poetry flattens the
|
|
39
|
+
# row onto the label (cn-field-label-card carries the border/
|
|
40
|
+
# padding the nested [data-slot=field] selectors keyed upstream).
|
|
41
|
+
# cn-label rides along because the card is a plain <label>, not
|
|
42
|
+
# the Label component (part-ownership: a nested data-component
|
|
43
|
+
# boundary would hand the card slots to Label).
|
|
44
|
+
element :card, "cn-label cn-field-label cn-field-label-card flex w-full items-center " \
|
|
45
|
+
"select-none has-data-disabled:opacity-50"
|
|
46
|
+
element :card_content, "cn-field-content group/field-content flex flex-1 flex-col " \
|
|
47
|
+
"leading-snug"
|
|
48
|
+
element :card_title, "cn-field-title flex w-fit items-center"
|
|
49
|
+
element :card_description, "cn-field-description"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%= content_tag(:div, **root_attributes.to_attributes) do %>
|
|
2
|
+
<% panel_defs.each_with_index do |panel, index| %>
|
|
3
|
+
<% unless index.zero? %>
|
|
4
|
+
<%= content_tag(:div, handle_attributes(index - 1)) do %>
|
|
5
|
+
<% if grip %><div class="<%= css(:grip) %>"></div><% end %>
|
|
6
|
+
<% end %>
|
|
7
|
+
<% end %>
|
|
8
|
+
<%= content_tag(:div, capture(&panel.block), panel_attributes(panel, index)) %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# A panel group divided by draggable splitter handles.
|
|
6
|
+
module Resizable
|
|
7
|
+
# A group of panels - side by side or stacked - divided by draggable
|
|
8
|
+
# splitter handles, so the reader can redistribute the space. Panels
|
|
9
|
+
# are flex children whose flex-grow IS the percentage, handles are
|
|
10
|
+
# role=separator splitters with full keyboard support (arrows step,
|
|
11
|
+
# Home/End jump), and the controller owns the drag + keyboard
|
|
12
|
+
# redistribution. Declare panels with with_panel; the component
|
|
13
|
+
# interleaves the handles and wires the ARIA.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# render Poetry::Ui::Resizable::Component.new(class: "h-48 rounded-lg border") do |group|
|
|
17
|
+
# group.with_panel(default_size: 25) { tag.div("Sidebar") }
|
|
18
|
+
# group.with_panel { tag.div("Content") }
|
|
19
|
+
# end
|
|
20
|
+
class Component < Poetry::Core::Component
|
|
21
|
+
# The closed vocabulary for the group-axis option.
|
|
22
|
+
DIRECTIONS = %i[horizontal vertical].freeze
|
|
23
|
+
|
|
24
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
25
|
+
AGENT_RULES = [
|
|
26
|
+
"Declare panels with with_panel(default_size:, min_size:, max_size:) - sizes are " \
|
|
27
|
+
"PERCENTAGES and the component interleaves the separator handles.",
|
|
28
|
+
"direction: :horizontal is side-by-side (the default); :vertical stacks.",
|
|
29
|
+
"Handles are keyboard splitters (arrows step, Home/End jump) - never replace them " \
|
|
30
|
+
"with styled divs.",
|
|
31
|
+
"Nest a group inside a panel for two-axis layouts - groups self-scope."
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
# The with_panel content-block requirement, stated statically for
|
|
35
|
+
# static checks.
|
|
36
|
+
SLOT_REQUIRED_CONTENT = { panel: "the panel content" }.freeze
|
|
37
|
+
|
|
38
|
+
# The same facts the before_render raise enforces, stated statically
|
|
39
|
+
# so static checks can flag too few panels without rendering.
|
|
40
|
+
REQUIRED_SLOTS = { panel: "at least two panels" }.freeze
|
|
41
|
+
|
|
42
|
+
renders_many :panels,
|
|
43
|
+
doc: "One panel per call: default_size/min_size/max_size are percentages of the group; the " \
|
|
44
|
+
"content block is required.",
|
|
45
|
+
renders: lambda { |default_size: nil, min_size: nil, max_size: nil, classes: nil, &block|
|
|
46
|
+
unless block
|
|
47
|
+
raise ArgumentError,
|
|
48
|
+
"Resizable with_panel requires a content block (the panel content)"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
panel_defs << Panel.new(default_size: default_size, min_size: min_size,
|
|
52
|
+
max_size: max_size, classes: classes, block: block)
|
|
53
|
+
nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
use_stimulus do
|
|
57
|
+
on :root do
|
|
58
|
+
controller :resizable do
|
|
59
|
+
register
|
|
60
|
+
value :orientation, from: :direction
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
# The full drag trio + keyboard on each handle; panels are found
|
|
64
|
+
# by DOM position (no targets).
|
|
65
|
+
on :handle do
|
|
66
|
+
controller :resizable do
|
|
67
|
+
action :drag_start, on: :pointerdown
|
|
68
|
+
action :drag_move, on: :pointermove
|
|
69
|
+
action :drag_end, on: :pointerup
|
|
70
|
+
action :keydown, on: :keydown
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
option :direction, :symbol, default: :horizontal,
|
|
76
|
+
doc: "The group axis: :horizontal lays panels side by side, :vertical stacks them."
|
|
77
|
+
option :grip, :boolean, default: false, doc: "Renders the grip dots on each handle."
|
|
78
|
+
|
|
79
|
+
validates :direction, inclusion: { in: DIRECTIONS }
|
|
80
|
+
|
|
81
|
+
part "resizable-panel-group", "The flex group root - the splitter controller rides here",
|
|
82
|
+
states: {
|
|
83
|
+
"data-orientation" => { condition: "the group axis", values: DIRECTIONS.map(&:to_s) }
|
|
84
|
+
}
|
|
85
|
+
part "resizable-panel", "One flex child whose flex-grow IS its percentage - the controller " \
|
|
86
|
+
"rewrites the inline flex on every resize",
|
|
87
|
+
states: {
|
|
88
|
+
"data-min-size" => "the panel's minimum percentage, rendered when with_panel passes " \
|
|
89
|
+
"min_size: (the controller's clamp floor)",
|
|
90
|
+
"data-max-size" => "the panel's maximum percentage, rendered when with_panel passes " \
|
|
91
|
+
"max_size: (the controller's clamp ceiling)"
|
|
92
|
+
}
|
|
93
|
+
part "resizable-handle", "The role=separator splitter between panels - drag and keyboard " \
|
|
94
|
+
"resizing live here; its aria-valuenow tracks the preceding panel"
|
|
95
|
+
|
|
96
|
+
# @api private
|
|
97
|
+
def before_render
|
|
98
|
+
# panels? forces the render block (the slot-predicate rule -
|
|
99
|
+
# panel_defs is empty until it runs).
|
|
100
|
+
raise ArgumentError, "Resizable requires at least two with_panel declarations" unless
|
|
101
|
+
panels? && panel_defs.size >= 2
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @api private
|
|
105
|
+
def panel_defs
|
|
106
|
+
@panel_defs ||= []
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Even shares when default_size: is omitted.
|
|
110
|
+
# @api private
|
|
111
|
+
def size_of(panel)
|
|
112
|
+
panel.default_size || (100.0 / panel_defs.size).round(2)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @api private
|
|
116
|
+
def root_attributes
|
|
117
|
+
html_attributes.merge_if_not_set(
|
|
118
|
+
{
|
|
119
|
+
"data-slot" => "resizable-panel-group", "data-orientation" => direction
|
|
120
|
+
}.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @api private
|
|
125
|
+
def panel_attributes(panel, index)
|
|
126
|
+
attrs = {
|
|
127
|
+
"id" => panel_id(index), "data-slot" => "resizable-panel",
|
|
128
|
+
"class" => [css(:panel), panel.classes].compact.join(" "),
|
|
129
|
+
"style" => "flex: #{size_of(panel)} 1 0px"
|
|
130
|
+
}
|
|
131
|
+
attrs["data-min-size"] = panel.min_size if panel.min_size
|
|
132
|
+
attrs["data-max-size"] = panel.max_size if panel.max_size
|
|
133
|
+
attrs
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# The splitter: a separator whose value tracks the PRECEDING
|
|
137
|
+
# panel; its visual orientation is PERPENDICULAR to the group axis
|
|
138
|
+
# (a side-by-side group has a vertical bar), which is also what
|
|
139
|
+
# the dictionary's aria-[orientation] selectors key on.
|
|
140
|
+
# @api private
|
|
141
|
+
def handle_attributes(index)
|
|
142
|
+
before = panel_defs[index]
|
|
143
|
+
{
|
|
144
|
+
"role" => "separator", "tabindex" => "0",
|
|
145
|
+
"data-slot" => "resizable-handle",
|
|
146
|
+
"aria-orientation" => direction == :horizontal ? "vertical" : "horizontal",
|
|
147
|
+
"aria-controls" => panel_id(index),
|
|
148
|
+
# Server-rendered initial value (the controller reconciles on
|
|
149
|
+
# connect) - the splitter announces even before JS.
|
|
150
|
+
"aria-valuenow" => size_of(before).round,
|
|
151
|
+
# The announced bounds fall back to 10/90 percent when the
|
|
152
|
+
# panel declares no min_size:/max_size: clamp.
|
|
153
|
+
"aria-valuemin" => before.min_size || 10,
|
|
154
|
+
"aria-valuemax" => before.max_size || 90,
|
|
155
|
+
"class" => css(:handle)
|
|
156
|
+
}.merge(stimulus_attributes_for(:handle))
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @api private
|
|
160
|
+
def panel_id(index)
|
|
161
|
+
"#{instance_id}-panel-#{index}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# One declared panel: its size bounds, extra classes, and content block.
|
|
165
|
+
# @api private
|
|
166
|
+
Panel = Data.define(:default_size, :min_size, :max_size, :classes, :block)
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def instance_id
|
|
171
|
+
@instance_id ||= poetry_instance_id("poetry-resizable")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private :panel_defs, :size_of, :root_attributes, :panel_attributes, :handle_attributes, :panel_id
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|