poetry-ui 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitattributes +6 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/THIRD_PARTY_NOTICES.md +201 -0
- data/app/components/poetry/ui/accordion/component.html.erb +3 -0
- data/app/components/poetry/ui/accordion/component.rb +197 -0
- data/app/components/poetry/ui/accordion/preview.rb +39 -0
- data/app/components/poetry/ui/accordion/style.rb +37 -0
- data/app/components/poetry/ui/alert/component.html.erb +6 -0
- data/app/components/poetry/ui/alert/component.rb +61 -0
- data/app/components/poetry/ui/alert/preview.rb +35 -0
- data/app/components/poetry/ui/alert/style.rb +24 -0
- data/app/components/poetry/ui/alert_dialog/component.html.erb +17 -0
- data/app/components/poetry/ui/alert_dialog/component.rb +208 -0
- data/app/components/poetry/ui/alert_dialog/preview.rb +48 -0
- data/app/components/poetry/ui/alert_dialog/style.rb +46 -0
- data/app/components/poetry/ui/aspect_ratio/component.rb +63 -0
- data/app/components/poetry/ui/aspect_ratio/preview.rb +29 -0
- data/app/components/poetry/ui/aspect_ratio/style.rb +13 -0
- data/app/components/poetry/ui/attachment/component.html.erb +13 -0
- data/app/components/poetry/ui/attachment/component.rb +139 -0
- data/app/components/poetry/ui/attachment/preview.rb +83 -0
- data/app/components/poetry/ui/attachment/style.rb +59 -0
- data/app/components/poetry/ui/autocomplete/component.html.erb +16 -0
- data/app/components/poetry/ui/autocomplete/component.rb +193 -0
- data/app/components/poetry/ui/autocomplete/preview.rb +39 -0
- data/app/components/poetry/ui/autocomplete/style.rb +18 -0
- data/app/components/poetry/ui/avatar/component.rb +103 -0
- data/app/components/poetry/ui/avatar/group_preview.html.erb +11 -0
- data/app/components/poetry/ui/avatar/preview.rb +50 -0
- data/app/components/poetry/ui/avatar/style.rb +34 -0
- data/app/components/poetry/ui/badge/component.rb +73 -0
- data/app/components/poetry/ui/badge/preview.rb +54 -0
- data/app/components/poetry/ui/badge/style.rb +35 -0
- data/app/components/poetry/ui/breadcrumb/component.html.erb +32 -0
- data/app/components/poetry/ui/breadcrumb/component.rb +102 -0
- data/app/components/poetry/ui/breadcrumb/dropdown_crumb_preview.html.erb +21 -0
- data/app/components/poetry/ui/breadcrumb/preview.rb +45 -0
- data/app/components/poetry/ui/breadcrumb/style.rb +25 -0
- data/app/components/poetry/ui/bubble/component.html.erb +4 -0
- data/app/components/poetry/ui/bubble/component.rb +106 -0
- data/app/components/poetry/ui/bubble/preview.rb +62 -0
- data/app/components/poetry/ui/bubble/style.rb +47 -0
- data/app/components/poetry/ui/button/component.html.erb +14 -0
- data/app/components/poetry/ui/button/component.rb +168 -0
- data/app/components/poetry/ui/button/preview.rb +94 -0
- data/app/components/poetry/ui/button/style.rb +41 -0
- data/app/components/poetry/ui/button_group/component.rb +70 -0
- data/app/components/poetry/ui/button_group/popup_triggers_preview.html.erb +21 -0
- data/app/components/poetry/ui/button_group/preview.rb +46 -0
- data/app/components/poetry/ui/button_group/style.rb +27 -0
- data/app/components/poetry/ui/button_group/text_prefix_preview.html.erb +7 -0
- data/app/components/poetry/ui/calendar/component.html.erb +72 -0
- data/app/components/poetry/ui/calendar/component.rb +366 -0
- data/app/components/poetry/ui/calendar/preview.rb +55 -0
- data/app/components/poetry/ui/calendar/style.rb +55 -0
- data/app/components/poetry/ui/card/component.html.erb +11 -0
- data/app/components/poetry/ui/card/component.rb +90 -0
- data/app/components/poetry/ui/card/preview.rb +29 -0
- data/app/components/poetry/ui/card/style.rb +23 -0
- data/app/components/poetry/ui/carousel/component.html.erb +17 -0
- data/app/components/poetry/ui/carousel/component.rb +179 -0
- data/app/components/poetry/ui/carousel/preview.rb +67 -0
- data/app/components/poetry/ui/carousel/style.rb +39 -0
- data/app/components/poetry/ui/checkbox/component.html.erb +22 -0
- data/app/components/poetry/ui/checkbox/component.rb +179 -0
- data/app/components/poetry/ui/checkbox/preview.rb +68 -0
- data/app/components/poetry/ui/checkbox/style.rb +28 -0
- data/app/components/poetry/ui/clipboard_text/component.html.erb +15 -0
- data/app/components/poetry/ui/clipboard_text/component.rb +154 -0
- data/app/components/poetry/ui/clipboard_text/preview.rb +24 -0
- data/app/components/poetry/ui/clipboard_text/style.rb +23 -0
- data/app/components/poetry/ui/code_block/component.html.erb +13 -0
- data/app/components/poetry/ui/code_block/component.rb +141 -0
- data/app/components/poetry/ui/code_block/preview.rb +40 -0
- data/app/components/poetry/ui/code_block/style.rb +38 -0
- data/app/components/poetry/ui/collapsible/component.html.erb +4 -0
- data/app/components/poetry/ui/collapsible/component.rb +134 -0
- data/app/components/poetry/ui/collapsible/preview.rb +24 -0
- data/app/components/poetry/ui/combobox/component.html.erb +49 -0
- data/app/components/poetry/ui/combobox/component.rb +1086 -0
- data/app/components/poetry/ui/combobox/preview.rb +164 -0
- data/app/components/poetry/ui/combobox/style.rb +148 -0
- data/app/components/poetry/ui/command/component.html.erb +20 -0
- data/app/components/poetry/ui/command/component.rb +526 -0
- data/app/components/poetry/ui/command/dialog_component.html.erb +14 -0
- data/app/components/poetry/ui/command/dialog_component.rb +206 -0
- data/app/components/poetry/ui/command/dialog_preview.rb +39 -0
- data/app/components/poetry/ui/command/preview.rb +121 -0
- data/app/components/poetry/ui/command/style.rb +78 -0
- data/app/components/poetry/ui/composable_trigger.rb +63 -0
- data/app/components/poetry/ui/context_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/context_menu/component.rb +358 -0
- data/app/components/poetry/ui/context_menu/preview.rb +111 -0
- data/app/components/poetry/ui/context_menu/style.rb +59 -0
- data/app/components/poetry/ui/data_table/component.html.erb +78 -0
- data/app/components/poetry/ui/data_table/component.rb +277 -0
- data/app/components/poetry/ui/data_table/preview.rb +68 -0
- data/app/components/poetry/ui/data_table/state.rb +115 -0
- data/app/components/poetry/ui/data_table/style.rb +42 -0
- data/app/components/poetry/ui/date_field/component.html.erb +7 -0
- data/app/components/poetry/ui/date_field/component.rb +209 -0
- data/app/components/poetry/ui/date_field/preview.rb +42 -0
- data/app/components/poetry/ui/date_field/style.rb +35 -0
- data/app/components/poetry/ui/date_picker/component.html.erb +23 -0
- data/app/components/poetry/ui/date_picker/component.rb +242 -0
- data/app/components/poetry/ui/date_picker/preview.rb +31 -0
- data/app/components/poetry/ui/date_picker/style.rb +25 -0
- data/app/components/poetry/ui/deferred/component.html.erb +16 -0
- data/app/components/poetry/ui/deferred/component.rb +90 -0
- data/app/components/poetry/ui/deferred/preview.rb +24 -0
- data/app/components/poetry/ui/dialog/component.html.erb +21 -0
- data/app/components/poetry/ui/dialog/component.rb +194 -0
- data/app/components/poetry/ui/dialog/preview.rb +33 -0
- data/app/components/poetry/ui/dialog/style.rb +30 -0
- data/app/components/poetry/ui/drawer/component.html.erb +16 -0
- data/app/components/poetry/ui/drawer/component.rb +207 -0
- data/app/components/poetry/ui/drawer/preview.rb +69 -0
- data/app/components/poetry/ui/drawer/style.rb +105 -0
- data/app/components/poetry/ui/dropdown_menu/component.html.erb +6 -0
- data/app/components/poetry/ui/dropdown_menu/component.rb +350 -0
- data/app/components/poetry/ui/dropdown_menu/preview.rb +101 -0
- data/app/components/poetry/ui/dropdown_menu/style.rb +60 -0
- data/app/components/poetry/ui/empty/component.html.erb +10 -0
- data/app/components/poetry/ui/empty/component.rb +79 -0
- data/app/components/poetry/ui/empty/preview.rb +27 -0
- data/app/components/poetry/ui/empty/style.rb +27 -0
- data/app/components/poetry/ui/family_identity.rb +52 -0
- data/app/components/poetry/ui/field/component.html.erb +19 -0
- data/app/components/poetry/ui/field/component.rb +183 -0
- data/app/components/poetry/ui/field/preview.html.erb +30 -0
- data/app/components/poetry/ui/field/preview.rb +72 -0
- data/app/components/poetry/ui/field/style.rb +68 -0
- data/app/components/poetry/ui/field_group/component.rb +61 -0
- data/app/components/poetry/ui/field_group/preview.html.erb +30 -0
- data/app/components/poetry/ui/field_group/preview.rb +20 -0
- data/app/components/poetry/ui/field_group/style.rb +25 -0
- data/app/components/poetry/ui/field_separator/component.rb +70 -0
- data/app/components/poetry/ui/field_separator/preview.rb +18 -0
- data/app/components/poetry/ui/field_separator/style.rb +20 -0
- data/app/components/poetry/ui/fieldset/component.rb +90 -0
- data/app/components/poetry/ui/fieldset/preview.html.erb +22 -0
- data/app/components/poetry/ui/fieldset/preview.rb +25 -0
- data/app/components/poetry/ui/fieldset/style.rb +18 -0
- data/app/components/poetry/ui/file_input/component.html.erb +14 -0
- data/app/components/poetry/ui/file_input/component.rb +188 -0
- data/app/components/poetry/ui/file_input/preview.rb +30 -0
- data/app/components/poetry/ui/file_input/style.rb +35 -0
- data/app/components/poetry/ui/hover_card/component.html.erb +6 -0
- data/app/components/poetry/ui/hover_card/component.rb +201 -0
- data/app/components/poetry/ui/hover_card/preview.rb +60 -0
- data/app/components/poetry/ui/hover_card/style.rb +17 -0
- data/app/components/poetry/ui/icon/component.rb +127 -0
- data/app/components/poetry/ui/icon/preview.rb +21 -0
- data/app/components/poetry/ui/input/component.rb +80 -0
- data/app/components/poetry/ui/input/preview.rb +35 -0
- data/app/components/poetry/ui/input/style.rb +18 -0
- data/app/components/poetry/ui/input_group/button_preview.html.erb +12 -0
- data/app/components/poetry/ui/input_group/component.rb +69 -0
- data/app/components/poetry/ui/input_group/preview.rb +23 -0
- data/app/components/poetry/ui/input_group/search_preview.html.erb +13 -0
- data/app/components/poetry/ui/input_group/style.rb +40 -0
- data/app/components/poetry/ui/input_group/textarea_preview.html.erb +10 -0
- data/app/components/poetry/ui/input_group_field.rb +61 -0
- data/app/components/poetry/ui/input_otp/component.html.erb +25 -0
- data/app/components/poetry/ui/input_otp/component.rb +250 -0
- data/app/components/poetry/ui/input_otp/preview.rb +91 -0
- data/app/components/poetry/ui/input_otp/style.rb +36 -0
- data/app/components/poetry/ui/item/component.html.erb +13 -0
- data/app/components/poetry/ui/item/component.rb +99 -0
- data/app/components/poetry/ui/item/group_preview.html.erb +15 -0
- data/app/components/poetry/ui/item/preview.rb +52 -0
- data/app/components/poetry/ui/item/style.rb +45 -0
- data/app/components/poetry/ui/kbd/component.rb +47 -0
- data/app/components/poetry/ui/kbd/group_preview.html.erb +4 -0
- data/app/components/poetry/ui/kbd/preview.rb +23 -0
- data/app/components/poetry/ui/kbd/style.rb +13 -0
- data/app/components/poetry/ui/label/component.rb +46 -0
- data/app/components/poetry/ui/label/preview.html.erb +5 -0
- data/app/components/poetry/ui/label/preview.rb +16 -0
- data/app/components/poetry/ui/label/style.rb +13 -0
- data/app/components/poetry/ui/link/component.rb +62 -0
- data/app/components/poetry/ui/link/preview.rb +31 -0
- data/app/components/poetry/ui/link/style.rb +20 -0
- data/app/components/poetry/ui/marker/component.html.erb +4 -0
- data/app/components/poetry/ui/marker/component.rb +81 -0
- data/app/components/poetry/ui/marker/preview.rb +43 -0
- data/app/components/poetry/ui/marker/style.rb +24 -0
- data/app/components/poetry/ui/menubar/component.html.erb +3 -0
- data/app/components/poetry/ui/menubar/component.rb +493 -0
- data/app/components/poetry/ui/menubar/preview.rb +164 -0
- data/app/components/poetry/ui/menubar/style.rb +71 -0
- data/app/components/poetry/ui/menus.rb +386 -0
- data/app/components/poetry/ui/message/component.html.erb +8 -0
- data/app/components/poetry/ui/message/component.rb +64 -0
- data/app/components/poetry/ui/message/preview.rb +33 -0
- data/app/components/poetry/ui/message/style.rb +29 -0
- data/app/components/poetry/ui/message_scroller/component.html.erb +15 -0
- data/app/components/poetry/ui/message_scroller/component.rb +163 -0
- data/app/components/poetry/ui/message_scroller/preview.rb +33 -0
- data/app/components/poetry/ui/message_scroller/style.rb +39 -0
- data/app/components/poetry/ui/metadata_list/component.rb +88 -0
- data/app/components/poetry/ui/metadata_list/preview.rb +55 -0
- data/app/components/poetry/ui/metadata_list/style.rb +35 -0
- data/app/components/poetry/ui/meter/component.rb +117 -0
- data/app/components/poetry/ui/meter/preview.rb +28 -0
- data/app/components/poetry/ui/meter/style.rb +16 -0
- data/app/components/poetry/ui/native_select/component.rb +124 -0
- data/app/components/poetry/ui/native_select/labelled_preview.html.erb +7 -0
- data/app/components/poetry/ui/native_select/preview.rb +20 -0
- data/app/components/poetry/ui/native_select/style.rb +22 -0
- data/app/components/poetry/ui/navigation_menu/bar_preview.html.erb +14 -0
- data/app/components/poetry/ui/navigation_menu/component.html.erb +25 -0
- data/app/components/poetry/ui/navigation_menu/component.rb +254 -0
- data/app/components/poetry/ui/navigation_menu/preview.rb +20 -0
- data/app/components/poetry/ui/navigation_menu/style.rb +73 -0
- data/app/components/poetry/ui/navigation_menu/viewport_preview.html.erb +23 -0
- data/app/components/poetry/ui/number_field/component.html.erb +19 -0
- data/app/components/poetry/ui/number_field/component.rb +263 -0
- data/app/components/poetry/ui/number_field/preview.rb +66 -0
- data/app/components/poetry/ui/number_field/style.rb +14 -0
- data/app/components/poetry/ui/pagination/component.html.erb +40 -0
- data/app/components/poetry/ui/pagination/component.rb +182 -0
- data/app/components/poetry/ui/pagination/preview.rb +47 -0
- data/app/components/poetry/ui/pagination/style.rb +27 -0
- data/app/components/poetry/ui/popover/component.html.erb +17 -0
- data/app/components/poetry/ui/popover/component.rb +203 -0
- data/app/components/poetry/ui/popover/preview.html.erb +29 -0
- data/app/components/poetry/ui/popover/preview.rb +66 -0
- data/app/components/poetry/ui/popover/style.rb +26 -0
- data/app/components/poetry/ui/popper_consumer.rb +69 -0
- data/app/components/poetry/ui/progress/component.rb +95 -0
- data/app/components/poetry/ui/progress/preview.rb +22 -0
- data/app/components/poetry/ui/progress/style.rb +19 -0
- data/app/components/poetry/ui/questionnaire/component.html.erb +80 -0
- data/app/components/poetry/ui/questionnaire/component.rb +396 -0
- data/app/components/poetry/ui/questionnaire/preview.rb +99 -0
- data/app/components/poetry/ui/questionnaire/style.rb +53 -0
- data/app/components/poetry/ui/radio_group/component.html.erb +4 -0
- data/app/components/poetry/ui/radio_group/component.rb +323 -0
- data/app/components/poetry/ui/radio_group/preview.rb +109 -0
- data/app/components/poetry/ui/radio_group/style.rb +53 -0
- data/app/components/poetry/ui/resizable/component.html.erb +10 -0
- data/app/components/poetry/ui/resizable/component.rb +178 -0
- data/app/components/poetry/ui/resizable/preview.rb +42 -0
- data/app/components/poetry/ui/resizable/style.rb +30 -0
- data/app/components/poetry/ui/scroll_area/component.rb +76 -0
- data/app/components/poetry/ui/scroll_area/preview.rb +35 -0
- data/app/components/poetry/ui/scroll_area/style.rb +19 -0
- data/app/components/poetry/ui/search_field/component.html.erb +15 -0
- data/app/components/poetry/ui/search_field/component.rb +137 -0
- data/app/components/poetry/ui/search_field/preview.rb +23 -0
- data/app/components/poetry/ui/search_field/style.rb +18 -0
- data/app/components/poetry/ui/select/component.html.erb +15 -0
- data/app/components/poetry/ui/select/component.rb +639 -0
- data/app/components/poetry/ui/select/preview.rb +113 -0
- data/app/components/poetry/ui/select/style.rb +58 -0
- data/app/components/poetry/ui/sensitive_input/component.html.erb +29 -0
- data/app/components/poetry/ui/sensitive_input/component.rb +274 -0
- data/app/components/poetry/ui/sensitive_input/preview.rb +40 -0
- data/app/components/poetry/ui/sensitive_input/style.rb +49 -0
- data/app/components/poetry/ui/separator/component.rb +57 -0
- data/app/components/poetry/ui/separator/preview.rb +19 -0
- data/app/components/poetry/ui/separator/style.rb +15 -0
- data/app/components/poetry/ui/sheet/component.html.erb +21 -0
- data/app/components/poetry/ui/sheet/component.rb +97 -0
- data/app/components/poetry/ui/sheet/preview.rb +57 -0
- data/app/components/poetry/ui/sheet/style.rb +47 -0
- data/app/components/poetry/ui/sidebar/component.html.erb +20 -0
- data/app/components/poetry/ui/sidebar/component.rb +249 -0
- data/app/components/poetry/ui/sidebar/preview.rb +21 -0
- data/app/components/poetry/ui/sidebar/shell_preview.html.erb +47 -0
- data/app/components/poetry/ui/sidebar/style.rb +139 -0
- data/app/components/poetry/ui/skeleton/component.rb +39 -0
- data/app/components/poetry/ui/skeleton/preview.rb +22 -0
- data/app/components/poetry/ui/skeleton/style.rb +13 -0
- data/app/components/poetry/ui/slider/component.html.erb +13 -0
- data/app/components/poetry/ui/slider/component.rb +360 -0
- data/app/components/poetry/ui/slider/preview.rb +73 -0
- data/app/components/poetry/ui/slider/style.rb +55 -0
- data/app/components/poetry/ui/spinner/component.rb +58 -0
- data/app/components/poetry/ui/spinner/preview.rb +22 -0
- data/app/components/poetry/ui/spinner/style.rb +12 -0
- data/app/components/poetry/ui/stat/component.html.erb +13 -0
- data/app/components/poetry/ui/stat/component.rb +102 -0
- data/app/components/poetry/ui/stat/preview.rb +47 -0
- data/app/components/poetry/ui/stat/style.rb +29 -0
- data/app/components/poetry/ui/switch/component.html.erb +15 -0
- data/app/components/poetry/ui/switch/component.rb +136 -0
- data/app/components/poetry/ui/switch/preview.rb +66 -0
- data/app/components/poetry/ui/switch/style.rb +22 -0
- data/app/components/poetry/ui/table/component.html.erb +3 -0
- data/app/components/poetry/ui/table/component.rb +93 -0
- data/app/components/poetry/ui/table/preview.rb +96 -0
- data/app/components/poetry/ui/table/style.rb +26 -0
- data/app/components/poetry/ui/tabs/component.html.erb +10 -0
- data/app/components/poetry/ui/tabs/component.rb +253 -0
- data/app/components/poetry/ui/tabs/preview.rb +43 -0
- data/app/components/poetry/ui/tabs/style.rb +49 -0
- data/app/components/poetry/ui/tag_group/component.html.erb +8 -0
- data/app/components/poetry/ui/tag_group/component.rb +203 -0
- data/app/components/poetry/ui/tag_group/preview.rb +43 -0
- data/app/components/poetry/ui/tag_group/style.rb +29 -0
- data/app/components/poetry/ui/textarea/component.rb +64 -0
- data/app/components/poetry/ui/textarea/preview.rb +75 -0
- data/app/components/poetry/ui/textarea/style.rb +18 -0
- data/app/components/poetry/ui/time_field/component.html.erb +6 -0
- data/app/components/poetry/ui/time_field/component.rb +85 -0
- data/app/components/poetry/ui/time_field/preview.rb +40 -0
- data/app/components/poetry/ui/time_field/style.rb +30 -0
- data/app/components/poetry/ui/timeline/component.rb +134 -0
- data/app/components/poetry/ui/timeline/preview.rb +61 -0
- data/app/components/poetry/ui/timeline/style.rb +54 -0
- data/app/components/poetry/ui/toast/component.html.erb +24 -0
- data/app/components/poetry/ui/toast/component.rb +178 -0
- data/app/components/poetry/ui/toast/preview.rb +69 -0
- data/app/components/poetry/ui/toast/style.rb +33 -0
- data/app/components/poetry/ui/toast_trigger/addressed_preview.html.erb +12 -0
- data/app/components/poetry/ui/toast_trigger/component.html.erb +3 -0
- data/app/components/poetry/ui/toast_trigger/component.rb +82 -0
- data/app/components/poetry/ui/toast_trigger/default_preview.html.erb +11 -0
- data/app/components/poetry/ui/toast_trigger/preview.rb +19 -0
- data/app/components/poetry/ui/toaster/component.html.erb +1 -0
- data/app/components/poetry/ui/toaster/component.rb +99 -0
- data/app/components/poetry/ui/toaster/positions_preview.html.erb +24 -0
- data/app/components/poetry/ui/toaster/preview.rb +46 -0
- data/app/components/poetry/ui/toaster/stamp_preview.html.erb +15 -0
- data/app/components/poetry/ui/toaster/style.rb +26 -0
- data/app/components/poetry/ui/toggle/component.rb +120 -0
- data/app/components/poetry/ui/toggle/preview.rb +79 -0
- data/app/components/poetry/ui/toggle/style.rb +33 -0
- data/app/components/poetry/ui/toggle_group/component.html.erb +4 -0
- data/app/components/poetry/ui/toggle_group/component.rb +252 -0
- data/app/components/poetry/ui/toggle_group/preview.rb +143 -0
- data/app/components/poetry/ui/toggle_group/style.rb +33 -0
- data/app/components/poetry/ui/toolbar/component.rb +125 -0
- data/app/components/poetry/ui/toolbar/preview.rb +41 -0
- data/app/components/poetry/ui/toolbar/style.rb +20 -0
- data/app/components/poetry/ui/tooltip/component.html.erb +20 -0
- data/app/components/poetry/ui/tooltip/component.rb +185 -0
- data/app/components/poetry/ui/tooltip/preview.rb +74 -0
- data/app/components/poetry/ui/tooltip/previews/provider_row.html.erb +25 -0
- data/app/components/poetry/ui/tooltip/style.rb +24 -0
- data/app/components/poetry/ui/tree/component.html.erb +32 -0
- data/app/components/poetry/ui/tree/component.rb +233 -0
- data/app/components/poetry/ui/tree/preview.rb +55 -0
- data/app/components/poetry/ui/tree/style.rb +29 -0
- data/app/components/poetry/ui/typeset/component.rb +73 -0
- data/app/components/poetry/ui/typeset/preview.rb +45 -0
- data/app/components/poetry/ui/typeset/style.rb +15 -0
- data/app/controllers/poetry/ui/llms_controller.rb +28 -0
- data/app/helpers/poetry/ui/components_helper.rb +1794 -0
- data/app/helpers/poetry/ui/form_builder/type_inference.rb +117 -0
- data/app/helpers/poetry/ui/form_builder.rb +916 -0
- data/app/helpers/poetry/ui/optimistic_form_builder.rb +55 -0
- data/app/helpers/poetry/ui/toast_stream_actions.rb +27 -0
- data/config/component_descriptions.yml +96 -0
- data/config/component_registry.yml +12257 -0
- data/config/locales/en.yml +93 -0
- data/config/routes.rb +8 -0
- data/config/template_classes.txt +113 -0
- data/lib/generators/poetry/add/add_generator.rb +218 -0
- data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +91 -0
- data/lib/generators/poetry/agents/agents_generator.rb +24 -0
- data/lib/generators/poetry/agents_section.rb +148 -0
- data/lib/generators/poetry/block/block_generator.rb +67 -0
- data/lib/generators/poetry/block/templates/action_bar.html.erb +45 -0
- data/lib/generators/poetry/block/templates/app_shell.html.erb +139 -0
- data/lib/generators/poetry/block/templates/data_index.html.erb +85 -0
- data/lib/generators/poetry/block/templates/destructive_panel.html.erb +26 -0
- data/lib/generators/poetry/block/templates/page_header.html.erb +25 -0
- data/lib/generators/poetry/block/templates/section_card.html.erb +42 -0
- data/lib/generators/poetry/block/templates/stepper.html.erb +79 -0
- data/lib/generators/poetry/block/templates/top_nav.html.erb +44 -0
- data/lib/generators/poetry/diff/diff_generator.rb +135 -0
- data/lib/generators/poetry/editor/editor_generator.rb +344 -0
- data/lib/generators/poetry/editor/templates/poetry-check-hook +81 -0
- data/lib/generators/poetry/install/install_generator.rb +364 -0
- data/lib/generators/poetry/pagination/pagination_generator.rb +76 -0
- data/lib/generators/poetry/pagination/templates/kaminari_paginator.html.erb +29 -0
- data/lib/generators/poetry/pagination/templates/poetry_link_renderer.rb +34 -0
- data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +28 -0
- data/lib/generators/poetry/recipes/agent_embed/app/javascript/controllers/agent_demo_controller.js +131 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/controllers/orders_controller.rb +15 -0
- data/lib/generators/poetry/recipes/screen_data_index/app/views/orders/index.html.erb +7 -0
- data/lib/generators/poetry/recipes/screen_data_index/test/system/orders_index_test.rb +16 -0
- data/lib/generators/poetry/recipes/screen_settings/app/controllers/settings_controller.rb +14 -0
- data/lib/generators/poetry/recipes/screen_settings/app/views/settings/show.html.erb +9 -0
- data/lib/generators/poetry/recipes/screen_settings/test/system/settings_show_test.rb +16 -0
- data/lib/generators/poetry/scaffold_templates/scaffold_templates_generator.rb +60 -0
- data/lib/generators/poetry/scaffold_templates/templates/_form.html.erb.tt +77 -0
- data/lib/generators/poetry/scaffold_templates/templates/controller.rb.tt +78 -0
- data/lib/generators/poetry/scaffold_templates/templates/edit.html.erb.tt +14 -0
- data/lib/generators/poetry/scaffold_templates/templates/index.html.erb.tt +24 -0
- data/lib/generators/poetry/scaffold_templates/templates/new.html.erb.tt +11 -0
- data/lib/generators/poetry/scaffold_templates/templates/partial.html.erb.tt +13 -0
- data/lib/generators/poetry/scaffold_templates/templates/show.html.erb.tt +18 -0
- data/lib/generators/poetry/skill/skill_generator.rb +34 -0
- data/lib/generators/poetry/skill/templates/poetry-component/SKILL.md +58 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/anatomy.md +152 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/checklist.md +67 -0
- data/lib/generators/poetry/skill/templates/poetry-component/references/documentation.md +137 -0
- data/lib/generators/poetry/skill/templates/poetry-design/SKILL.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/audit.md +123 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/compose.md +73 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/figma.md +68 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/paper.md +56 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/study.md +47 -0
- data/lib/generators/poetry/skill/templates/poetry-design/references/theme.md +70 -0
- data/lib/generators/poetry/skills_section.rb +58 -0
- data/lib/poetry/ui/chat.rb +283 -0
- data/lib/poetry/ui/code_block_highlighter.rb +60 -0
- data/lib/poetry/ui/engine.rb +55 -0
- data/lib/poetry/ui/recipes.rb +157 -0
- data/lib/poetry/ui/testing/combobox.rb +167 -0
- data/lib/poetry/ui/testing/dialog.rb +73 -0
- data/lib/poetry/ui/testing/menu.rb +127 -0
- data/lib/poetry/ui/testing/registration.rb +58 -0
- data/lib/poetry/ui/testing/select.rb +147 -0
- data/lib/poetry/ui/testing/tester.rb +130 -0
- data/lib/poetry/ui/testing.rb +130 -0
- data/lib/poetry/ui/themes.rb +109 -0
- data/lib/poetry/ui/version.rb +8 -0
- data/lib/poetry/ui/webmcp.rb +91 -0
- data/lib/poetry/ui.rb +285 -0
- data/lib/poetry-ui.rb +5 -0
- data/lib/tasks/poetry/check.rake +73 -0
- data/lib/tasks/poetry/design.rake +293 -0
- data/lib/tasks/poetry/verify.rake +36 -0
- data/themes/default.css +1793 -0
- data/themes/luma.css +1764 -0
- data/themes/lyra.css +1768 -0
- data/themes/maia.css +1768 -0
- data/themes/mira.css +1760 -0
- data/themes/nova.css +1755 -0
- data/themes/rhea.css +1771 -0
- data/themes/sera.css +1757 -0
- data/themes/vega.css +1775 -0
- data/typeset/typeset.css +499 -0
- metadata +502 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# The InputGroup family - one bordered field surface for a control plus addons.
|
|
6
|
+
module InputGroup
|
|
7
|
+
# The InputGroup - one bordered field surface holding a control plus
|
|
8
|
+
# addons (icons, text, kbd hints, tiny buttons). The GROUP wears the
|
|
9
|
+
# border and focus ring; the control inside is borderless
|
|
10
|
+
# (poetry_input_group_input / _textarea strip the Input's own chrome
|
|
11
|
+
# and stamp data-slot=input-group-control, which the group's
|
|
12
|
+
# focus-within/invalid selectors key on). Addons align inline
|
|
13
|
+
# (start/end) or block (start/end - full-width rows).
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# <%= poetry_input_group do %>
|
|
17
|
+
# <%= poetry_input_group_addon do %>
|
|
18
|
+
# <%= poetry_icon(name: :search) %>
|
|
19
|
+
# <% end %>
|
|
20
|
+
# <%= poetry_input_group_input(name: "q", placeholder: "Search...") %>
|
|
21
|
+
# <% end %>
|
|
22
|
+
class Component < Poetry::Core::Component
|
|
23
|
+
requires_content "its control + addons"
|
|
24
|
+
|
|
25
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
26
|
+
AGENT_RULES = [
|
|
27
|
+
"The control INSIDE must be poetry_input_group_input/_textarea - a plain poetry_input " \
|
|
28
|
+
"keeps its own border+ring and double-chromes the group.",
|
|
29
|
+
"Addons are poetry_input_group_addon(align:) wrapping icons/text/buttons; use " \
|
|
30
|
+
"poetry_input_group_text for muted captions and poetry_input_group_button for tiny actions.",
|
|
31
|
+
"The group is a surface, not a label - the control still needs its Label/Field pairing."
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
part "input-group", "The bordered group surface (role=group) - wears the border, the " \
|
|
35
|
+
"focus-within ring, and the invalid ring for the borderless " \
|
|
36
|
+
"control inside"
|
|
37
|
+
part "input-group-addon", "One addon cell (icons, text, kbd hints, tiny buttons) " \
|
|
38
|
+
"rendered by poetry_input_group_addon around the control",
|
|
39
|
+
states: {
|
|
40
|
+
"data-align" => { condition: "always - the addon's align: axis (inline rides " \
|
|
41
|
+
"the row, block takes a full-width row)",
|
|
42
|
+
values: %w[inline-start inline-end block-start block-end] }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Enforces the required content block before render.
|
|
46
|
+
# @api private
|
|
47
|
+
def before_render
|
|
48
|
+
ensure_content!
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Renders the group surface around the content.
|
|
52
|
+
# @api private
|
|
53
|
+
def call
|
|
54
|
+
content_tag(:div, content, **root_attributes.to_attributes)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The group surface's attributes.
|
|
58
|
+
# @api private
|
|
59
|
+
def root_attributes
|
|
60
|
+
html_attributes.merge_if_not_set(
|
|
61
|
+
{ "role" => "group", "data-slot" => "input-group" }.merge(component_data_attributes)
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private :root_attributes
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module InputGroup
|
|
6
|
+
# The InputGroup preview: the search pattern (icon + control + kbd),
|
|
7
|
+
# a button addon, and the block-aligned textarea form.
|
|
8
|
+
class Preview < Poetry::Core::Preview::Base
|
|
9
|
+
def default
|
|
10
|
+
render_with_template(template: "poetry/ui/input_group/search_preview")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def with_button
|
|
14
|
+
render_with_template(template: "poetry/ui/input_group/button_preview")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def textarea_block
|
|
18
|
+
render_with_template(template: "poetry/ui/input_group/textarea_preview")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%# The search pattern: leading icon, borderless control, trailing kbd. %>
|
|
2
|
+
<div class="w-80 p-4">
|
|
3
|
+
<%= poetry_label(for_id: "input-group-search", class: "sr-only") { "Search" } %>
|
|
4
|
+
<%= poetry_input_group do %>
|
|
5
|
+
<%= poetry_input_group_addon do %>
|
|
6
|
+
<%= poetry_icon(name: :search) %>
|
|
7
|
+
<% end %>
|
|
8
|
+
<%= poetry_input_group_input(id: "input-group-search", name: "q", placeholder: "Search…") %>
|
|
9
|
+
<%= poetry_input_group_addon(align: :"inline-end") do %>
|
|
10
|
+
<%= poetry_kbd { "⌘K" } %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module InputGroup
|
|
6
|
+
# Re-expressed through the cn-* theme layer. The structural
|
|
7
|
+
# reflow chain (block addons force h-auto/flex-col) stays inline -
|
|
8
|
+
# it is layout mechanism; the chrome (border, focus-within ring,
|
|
9
|
+
# invalid ring, paddings) rides themes/default.css. Upstream's addon
|
|
10
|
+
# click-to-focus handler remains skipped (static port).
|
|
11
|
+
class Style < Poetry::Core::Style
|
|
12
|
+
base "cn-input-group group/input-group relative flex w-full min-w-0 items-center outline-none " \
|
|
13
|
+
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col " \
|
|
14
|
+
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col " \
|
|
15
|
+
"has-[>textarea]:h-auto"
|
|
16
|
+
|
|
17
|
+
# The addon: shared chrome + one align axis (inline = in the row,
|
|
18
|
+
# block = its own full-width row).
|
|
19
|
+
element :addon, "cn-input-group-addon flex cursor-text items-center justify-center select-none"
|
|
20
|
+
element :addon_inline_start, "cn-input-group-addon-align-inline-start order-first"
|
|
21
|
+
element :addon_inline_end, "cn-input-group-addon-align-inline-end order-last"
|
|
22
|
+
element :addon_block_start, "cn-input-group-addon-align-block-start order-first w-full justify-start"
|
|
23
|
+
element :addon_block_end, "cn-input-group-addon-align-block-end order-last w-full justify-start"
|
|
24
|
+
|
|
25
|
+
element :text, "cn-input-group-text flex items-center [&_svg]:pointer-events-none"
|
|
26
|
+
|
|
27
|
+
# The borderless control overrides (merged over Input/Textarea).
|
|
28
|
+
element :control_input, "cn-input-group-input flex-1"
|
|
29
|
+
element :control_textarea, "cn-input-group-textarea flex-1 resize-none"
|
|
30
|
+
|
|
31
|
+
# The tiny in-group Button sizes (merged over the ghost Button).
|
|
32
|
+
element :button, "cn-input-group-button flex items-center shadow-none"
|
|
33
|
+
element :button_xs, "cn-input-group-button-size-xs"
|
|
34
|
+
element :button_sm, "cn-input-group-button-size-sm"
|
|
35
|
+
element :button_icon_xs, "cn-input-group-button-size-icon-xs"
|
|
36
|
+
element :button_icon_sm, "cn-input-group-button-size-icon-sm"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<%# The block form: a textarea with a full-width footer row. %>
|
|
2
|
+
<div class="w-80 p-4">
|
|
3
|
+
<%= poetry_label(for_id: "input-group-notes", class: "sr-only") { "Notes" } %>
|
|
4
|
+
<%= poetry_input_group do %>
|
|
5
|
+
<%= poetry_input_group_textarea(id: "input-group-notes", name: "notes", placeholder: "Add a note…") %>
|
|
6
|
+
<%= poetry_input_group_addon(align: :"block-end") do %>
|
|
7
|
+
<%= poetry_input_group_text { "Markdown supported" } %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# Gives a field component InputGroup's chrome: the control-id
|
|
6
|
+
# ladder, the bordered group shell, the addon cells, and the ghost
|
|
7
|
+
# tool-button recipe (steppers, clear, reveal, copy). Used by the
|
|
8
|
+
# affix-field controls (NumberField, SearchField, SensitiveInput);
|
|
9
|
+
# each includer keeps its own visible-input skeleton, since those
|
|
10
|
+
# attribute sets genuinely differ.
|
|
11
|
+
module InputGroupField
|
|
12
|
+
include FamilyIdentity
|
|
13
|
+
|
|
14
|
+
# The control-id ladder: a caller id wins, else the family
|
|
15
|
+
# instance seed.
|
|
16
|
+
# @api private
|
|
17
|
+
def control_id
|
|
18
|
+
@control_id ||= id.presence || instance_id
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The bordered role=group shell wearing InputGroup's chrome.
|
|
22
|
+
# @api private
|
|
23
|
+
def group_attributes
|
|
24
|
+
{
|
|
25
|
+
"role" => "group",
|
|
26
|
+
"data-slot" => "#{family_slot_prefix}-group",
|
|
27
|
+
"class" => InputGroup::Style.css
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# One addon cell aligned inline-start or inline-end on the group row.
|
|
32
|
+
# @api private
|
|
33
|
+
def addon_attributes(align)
|
|
34
|
+
{
|
|
35
|
+
"data-slot" => "input-group-addon",
|
|
36
|
+
"data-align" => "inline-#{align}",
|
|
37
|
+
"class" => InputGroup::Style.css(:addon, class: InputGroup::Style.css(:"addon_inline_#{align}"))
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# The ghost icon-xs tool button riding the group chrome (steppers,
|
|
44
|
+
# clear, reveal, copy). tabindex -1 by default (keyboard users act
|
|
45
|
+
# on the control); nil drops it (SensitiveInput's copy is a real
|
|
46
|
+
# tab stop).
|
|
47
|
+
def group_tool_button(slot:, label:, wiring:, tabindex: "-1", extra: {})
|
|
48
|
+
Button::Component.new({
|
|
49
|
+
variant: :ghost, size: :"icon-xs", disabled: disabled, label: label,
|
|
50
|
+
class: InputGroup::Style.css(:button, class: InputGroup::Style.css(:button_icon_xs)),
|
|
51
|
+
"data-slot" => slot,
|
|
52
|
+
"tabindex" => tabindex,
|
|
53
|
+
**extra,
|
|
54
|
+
"aria-controls" => control_id
|
|
55
|
+
}.compact.merge(wiring))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private :control_id, :group_attributes, :addon_attributes
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%# ONE real input over aria-hidden mirror cells. The char renders as the
|
|
2
|
+
slot's LEADING text node (the controller repaints the same node); the
|
|
3
|
+
caret element hides server-side - it exists only for the active EMPTY
|
|
4
|
+
cell under JS. Cells count from 0 across groups (slot order == string
|
|
5
|
+
index order; the row is dir=ltr). %>
|
|
6
|
+
<%= content_tag(:div, **root_attributes.to_attributes) do %>
|
|
7
|
+
<%= tag.input(**input_attributes.to_attributes) %>
|
|
8
|
+
<% cell = -1 %>
|
|
9
|
+
<% group_sizes.each_with_index do |group_size, group_index| %>
|
|
10
|
+
<div data-slot="input-otp-group" aria-hidden="true" class="<%= css(:group) %>">
|
|
11
|
+
<% group_size.times do %>
|
|
12
|
+
<% cell += 1 %>
|
|
13
|
+
<%= content_tag(:div, **slot_attributes(cell)) do %><%= char_at(cell) %><div data-slot="input-otp-caret" hidden class="<%= css(:caret) %>"><div class="<%= css(:caret_bar) %>"></div></div><% end %>
|
|
14
|
+
<% end %>
|
|
15
|
+
</div>
|
|
16
|
+
<% if separator && group_index < group_sizes.size - 1 %>
|
|
17
|
+
<%# aria-hidden DESPITE the source's bare role=separator - between
|
|
18
|
+
chunks of ONE field a semantic separator is AT noise (recorded
|
|
19
|
+
divergence; the role stays for styling/self-id parity). %>
|
|
20
|
+
<div data-slot="input-otp-separator" class="<%= css(:separator) %>" role="separator" aria-hidden="true">
|
|
21
|
+
<%= render Poetry::Ui::Icon::Component.new(name: :minus) %>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% end %>
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# The InputOtp family - fixed-length one-time-code entry.
|
|
6
|
+
module InputOtp
|
|
7
|
+
# Fixed-length one-time-code entry built around a single input:
|
|
8
|
+
# there are NO per-cell inputs. One real
|
|
9
|
+
# native <input> (autocomplete one-time-code) holds the whole value,
|
|
10
|
+
# stretched invisibly over the row, so paste, SMS autofill, IME,
|
|
11
|
+
# constraint validation and serialization are all native and
|
|
12
|
+
# assistive technology
|
|
13
|
+
# sees ONE text field. The n slot cells are a purely presentational
|
|
14
|
+
# aria-hidden MIRROR painted by JS: auto-advance and
|
|
15
|
+
# backspace-retreat are not features, they are the native caret
|
|
16
|
+
# PROJECTED (data-active follows selectionStart).
|
|
17
|
+
#
|
|
18
|
+
# The server renders the value's chars into the cells AND the input
|
|
19
|
+
# (no-JS honesty: typing/submitting works JS-free; only the live
|
|
20
|
+
# cell paint needs the controller). The cell row forces dir=ltr -
|
|
21
|
+
# codes are LTR strings, slot order == string index order even on
|
|
22
|
+
# RTL pages.
|
|
23
|
+
#
|
|
24
|
+
# @example
|
|
25
|
+
# render Poetry::Ui::InputOtp::Component.new(name: "code", length: 6, groups: [3, 3])
|
|
26
|
+
class Component < Poetry::Core::Component
|
|
27
|
+
# The supported code lengths - cell UIs degrade past ~8.
|
|
28
|
+
LENGTH_RANGE = (1..12)
|
|
29
|
+
# The built-in per-char filters with their native pattern + inputmode.
|
|
30
|
+
PATTERNS = {
|
|
31
|
+
digits: { js: "\\d", char: "[0-9]", inputmode: "numeric" },
|
|
32
|
+
alphanumeric: { js: "[a-zA-Z0-9]", char: "[a-zA-Z0-9]", inputmode: "text" }
|
|
33
|
+
}.freeze
|
|
34
|
+
# Field control_attributes land on the INPUT (the real control);
|
|
35
|
+
# everything else the caller passes styles the container.
|
|
36
|
+
INPUT_FACING = %w[id aria-label aria-describedby aria-invalid aria-required].freeze
|
|
37
|
+
|
|
38
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
39
|
+
AGENT_RULES = [
|
|
40
|
+
"Use poetry_input_otp / form.otp_field - NEVER build per-cell inputs (n Tab stops, broken " \
|
|
41
|
+
"paste, broken SMS autofill, unnameable cells).",
|
|
42
|
+
"Label via Field always ('Verification code'); put the length in the hint.",
|
|
43
|
+
"groups must sum to length (ArgumentError).",
|
|
44
|
+
"Do NOT auto-submit on poetry:otp:complete without a visible confirm affordance - silent " \
|
|
45
|
+
"submit on the 6th keystroke strands users who mistyped char 3.",
|
|
46
|
+
"Never pre-fill value: with a real code in previews/test fixtures beyond dummies; never log " \
|
|
47
|
+
"the value (it is a live credential).",
|
|
48
|
+
"InputOTP is for CODES - passwords use Input type=password, longer identifiers use Input."
|
|
49
|
+
].freeze
|
|
50
|
+
|
|
51
|
+
use_stimulus do
|
|
52
|
+
on :root do
|
|
53
|
+
controller :otp do
|
|
54
|
+
register
|
|
55
|
+
value :length
|
|
56
|
+
value :pattern, from: :pattern_js
|
|
57
|
+
# The input already covers the cells at z-20; this catches
|
|
58
|
+
# gap/separator clicks.
|
|
59
|
+
action :focus_input, on: :click
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
on :input do
|
|
63
|
+
controller :otp do
|
|
64
|
+
target :input
|
|
65
|
+
action :sync, on: %i[input focus blur]
|
|
66
|
+
# maxlength truncates RAW clipboard text before the input
|
|
67
|
+
# event - the controller filters the paste itself.
|
|
68
|
+
action :paste, on: :paste
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
on :slot do
|
|
72
|
+
controller(:otp) { target :slot }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
option :name, :string, required: true, doc: "The ONE input serializes params[name] = the code string."
|
|
77
|
+
option :length, :integer, required: true, doc: "Code length = slot count = maxlength."
|
|
78
|
+
option :value, :string,
|
|
79
|
+
doc: "Current code (server-rendered into the input AND the cells). The FormBuilder deliberately never " \
|
|
80
|
+
"round-trips it (a rejected code is dead)."
|
|
81
|
+
option :groups, ActiveModel::Type::Value.new,
|
|
82
|
+
doc: "Cell clustering, e.g. [3, 3] -> two groups with a separator."
|
|
83
|
+
option :pattern, ActiveModel::Type::Value.new, default: :digits,
|
|
84
|
+
doc: ":digits (numeric keypad) | :alphanumeric | a custom " \
|
|
85
|
+
"Regexp - the per-char filter + the native pattern " \
|
|
86
|
+
"attribute + inputmode."
|
|
87
|
+
option :disabled, :boolean, default: false, doc: "Disables the native input (the whole row dims)."
|
|
88
|
+
option :required, :boolean, default: false,
|
|
89
|
+
doc: "aria-required on the input - never native required (the Field rule: " \
|
|
90
|
+
"required rides server-side validation + aria)."
|
|
91
|
+
option :invalid, :boolean, default: false,
|
|
92
|
+
doc: "aria-invalid on the input; the cells mirror the destructive treatment (set " \
|
|
93
|
+
"by Field/FormBuilder from the failed verify)."
|
|
94
|
+
option :separator, :boolean, default: true,
|
|
95
|
+
doc: "role=separator dash between groups (meaningful with 2+ groups)."
|
|
96
|
+
|
|
97
|
+
part "input-otp-container", "Root row (forced dir=ltr - slot order equals string index " \
|
|
98
|
+
"order even on RTL pages) wrapping the real input and the " \
|
|
99
|
+
"mirror cells"
|
|
100
|
+
part "input-otp", "THE real native <input> (autocomplete one-time-code) stretched " \
|
|
101
|
+
"invisibly over the row - the only AT and serialization surface"
|
|
102
|
+
part "input-otp-group", "One aria-hidden cluster of mirror cells (groups: clustering)"
|
|
103
|
+
part "input-otp-slot", "One presentational mirror cell - paints its char and the " \
|
|
104
|
+
"active-cell ring",
|
|
105
|
+
states: {
|
|
106
|
+
"data-active" => { condition: "\"true\" while the native caret sits on this cell " \
|
|
107
|
+
"(the controller projects selectionStart while the " \
|
|
108
|
+
"input is focused; the server renders \"false\")",
|
|
109
|
+
values: %w[true false] }
|
|
110
|
+
}
|
|
111
|
+
part "input-otp-caret", "The fake-caret overlay - hidden server-side; the controller " \
|
|
112
|
+
"unhides it on the active EMPTY cell"
|
|
113
|
+
part "input-otp-separator", "The between-groups dash - role=separator kept for parity " \
|
|
114
|
+
"but aria-hidden (a recorded divergence)"
|
|
115
|
+
|
|
116
|
+
# Validates length, groups, and pattern up front.
|
|
117
|
+
# @api private
|
|
118
|
+
def initialize(attributes = {})
|
|
119
|
+
super
|
|
120
|
+
|
|
121
|
+
validate_length!
|
|
122
|
+
validate_groups!
|
|
123
|
+
pattern_spec # raises on garbage patterns up front
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The resolved cell clustering ([length] when groups: is absent).
|
|
127
|
+
# @api private
|
|
128
|
+
def group_sizes
|
|
129
|
+
@group_sizes ||= (groups.presence || [length]).map { |size| Integer(size) }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The value truncated to length, as rendered into input and cells.
|
|
133
|
+
# @api private
|
|
134
|
+
def display_value
|
|
135
|
+
value.to_s[0, length].to_s
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The character painted into the cell at index ("" when empty).
|
|
139
|
+
# @api private
|
|
140
|
+
def char_at(index)
|
|
141
|
+
display_value[index].to_s
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# The native input's server-stable id.
|
|
145
|
+
# @api private
|
|
146
|
+
def input_id
|
|
147
|
+
@input_id ||= poetry_instance_id("poetry-input-otp")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Whether the rendered value already fills every cell.
|
|
151
|
+
# @api private
|
|
152
|
+
def complete?
|
|
153
|
+
display_value.length >= length
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The row container's attributes.
|
|
157
|
+
# @api private
|
|
158
|
+
def root_attributes
|
|
159
|
+
attrs = {
|
|
160
|
+
"data-slot" => "input-otp-container",
|
|
161
|
+
# Codes are LTR strings: slot order must equal string index
|
|
162
|
+
# order even on RTL pages (the one place forcing ltr is right).
|
|
163
|
+
"dir" => "ltr"
|
|
164
|
+
}
|
|
165
|
+
container_attributes.merge_if_not_set(
|
|
166
|
+
attrs.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# The real native input's attributes.
|
|
171
|
+
# @api private
|
|
172
|
+
def input_attributes
|
|
173
|
+
attrs = {
|
|
174
|
+
"type" => "text", "name" => name, "id" => input_id,
|
|
175
|
+
"maxlength" => length, "pattern" => html_pattern, "inputmode" => inputmode,
|
|
176
|
+
"autocomplete" => "one-time-code", "spellcheck" => "false",
|
|
177
|
+
"autocapitalize" => "off", "autocorrect" => "off",
|
|
178
|
+
"data-slot" => "input-otp", "class" => css(:input)
|
|
179
|
+
}
|
|
180
|
+
# The invisible input is the ONLY AT surface - it must always
|
|
181
|
+
# carry a name (the axe label rule); callers override via
|
|
182
|
+
# aria-label / Field labelling.
|
|
183
|
+
attrs["aria-label"] = t("poetry.input_otp.label")
|
|
184
|
+
attrs["value"] = display_value if display_value.present?
|
|
185
|
+
attrs["disabled"] = true if disabled
|
|
186
|
+
attrs["aria-required"] = true if required
|
|
187
|
+
attrs["aria-invalid"] = true if invalid
|
|
188
|
+
Poetry::Core::HTML::Attributes.new(html_attributes.slice(*INPUT_FACING))
|
|
189
|
+
.merge_if_not_set(attrs.merge(stimulus_attributes_for(:input)))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# One mirror cell's attributes.
|
|
193
|
+
# @api private
|
|
194
|
+
def slot_attributes(_index)
|
|
195
|
+
attrs = {
|
|
196
|
+
class: css(:slot), "data-slot" => "input-otp-slot", "data-active" => "false"
|
|
197
|
+
}
|
|
198
|
+
# Styling-only on an aria-hidden cell (the source's
|
|
199
|
+
# aria-invalid: variants key on it); AT never sees it.
|
|
200
|
+
attrs["aria-invalid"] = "true" if invalid
|
|
201
|
+
attrs.merge(stimulus_attributes_for(:slot))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
# Everything NOT input-facing styles the container.
|
|
207
|
+
def container_attributes
|
|
208
|
+
Poetry::Core::HTML::Attributes.new(html_attributes.except(*INPUT_FACING))
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def pattern_spec
|
|
212
|
+
return PATTERNS.fetch(pattern.to_sym) if pattern.respond_to?(:to_sym) && PATTERNS.key?(pattern.to_sym)
|
|
213
|
+
return { js: pattern.source, char: "(?:#{pattern.source})", inputmode: "text" } if pattern.is_a?(Regexp)
|
|
214
|
+
|
|
215
|
+
raise ArgumentError, "InputOTP pattern: must be :digits, :alphanumeric, or a Regexp - " \
|
|
216
|
+
"got #{pattern.inspect}"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def html_pattern
|
|
220
|
+
"#{pattern_spec[:char]}{#{length}}"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def inputmode
|
|
224
|
+
pattern_spec[:inputmode]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def validate_length!
|
|
228
|
+
return if LENGTH_RANGE.cover?(length)
|
|
229
|
+
|
|
230
|
+
raise ArgumentError, "InputOTP length: must be in #{LENGTH_RANGE} (cell UIs degrade past ~8) - " \
|
|
231
|
+
"got #{length.inspect}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def validate_groups!
|
|
235
|
+
return if group_sizes.sum == length
|
|
236
|
+
|
|
237
|
+
raise ArgumentError, "InputOTP groups: must sum to length (#{length}) - " \
|
|
238
|
+
"#{group_sizes.inspect} sums to #{group_sizes.sum}"
|
|
239
|
+
rescue TypeError
|
|
240
|
+
raise ArgumentError, "InputOTP groups: must be an array of integers - got #{groups.inspect}"
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def pattern_js = pattern_spec[:js]
|
|
244
|
+
|
|
245
|
+
private :group_sizes, :display_value, :char_at, :input_id, :complete?, :root_attributes, :input_attributes
|
|
246
|
+
private :slot_attributes
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module InputOtp
|
|
6
|
+
# The InputOTP preview matrix: the four ported examples (demo,
|
|
7
|
+
# pattern, controlled, separator) plus fill states, invalid, and
|
|
8
|
+
# disabled. Values are DUMMIES only - never a real code (the value
|
|
9
|
+
# is a live credential in production).
|
|
10
|
+
class Preview < Poetry::Core::Preview::Base
|
|
11
|
+
# @!group Groupings
|
|
12
|
+
|
|
13
|
+
# The input-otp-demo port: 6 digits as [3, 3] with a separator.
|
|
14
|
+
def default
|
|
15
|
+
render_component(name: "code", length: 6, groups: [3, 3],
|
|
16
|
+
"aria-label": "Verification code")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# One group of six (no separator rendered for a single group).
|
|
20
|
+
def single_group
|
|
21
|
+
render_component(name: "code", length: 6, "aria-label": "Verification code")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The separator example's [2, 2, 2] shape.
|
|
25
|
+
def three_groups
|
|
26
|
+
render_component(name: "code", length: 6, groups: [2, 2, 2],
|
|
27
|
+
"aria-label": "Verification code")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @!endgroup
|
|
31
|
+
|
|
32
|
+
# @!group Fill states
|
|
33
|
+
|
|
34
|
+
# Server-rendered partial value (no-JS honesty: the chars paint
|
|
35
|
+
# server-side; the caret cell needs the controller).
|
|
36
|
+
def partial
|
|
37
|
+
render_component(name: "code", length: 6, groups: [3, 3], value: "12",
|
|
38
|
+
"aria-label": "Verification code")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def complete
|
|
42
|
+
render_component(name: "code", length: 6, groups: [3, 3], value: "123456",
|
|
43
|
+
"aria-label": "Verification code")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @!endgroup
|
|
47
|
+
|
|
48
|
+
# @!group States
|
|
49
|
+
|
|
50
|
+
# The alphanumeric pattern (input-otp-pattern parity):
|
|
51
|
+
# inputmode=text, autocapitalize off.
|
|
52
|
+
def alphanumeric
|
|
53
|
+
render_component(name: "code", length: 6, pattern: :alphanumeric,
|
|
54
|
+
"aria-label": "Verification code")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def disabled
|
|
58
|
+
render_component(name: "code", length: 6, groups: [3, 3], disabled: true,
|
|
59
|
+
"aria-label": "Verification code")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The failed-verify re-render: aria-invalid on the input, the
|
|
63
|
+
# destructive treatment on the cells, value BLANKED (the
|
|
64
|
+
# otp_field default - a rejected code is dead).
|
|
65
|
+
def invalid
|
|
66
|
+
render_component(name: "code", length: 6, groups: [3, 3], invalid: true,
|
|
67
|
+
"aria-label": "Verification code")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @!endgroup
|
|
71
|
+
|
|
72
|
+
# @!group Recipes
|
|
73
|
+
|
|
74
|
+
# The Field recipe: label 'Verification code', length in the hint,
|
|
75
|
+
# id/describedby landing on the INPUT (the real control).
|
|
76
|
+
def in_a_field
|
|
77
|
+
field = Field::Component.new(
|
|
78
|
+
id: "preview-code", label_text: "Verification code",
|
|
79
|
+
hint: "Enter the 6-digit code we sent to your phone."
|
|
80
|
+
)
|
|
81
|
+
render_component(field) do
|
|
82
|
+
embed(Component.new(name: "code", length: 6, groups: [3, 3],
|
|
83
|
+
**field.control_attributes.transform_keys(&:to_sym)))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @!endgroup
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module InputOtp
|
|
6
|
+
# Re-expressed through the cn-* theme layer. The invisible-
|
|
7
|
+
# control mechanism stays ENTIRELY inline (:input stretched over the
|
|
8
|
+
# slot row at opacity 0.005 - clickable, focusable, AT-visible; NEVER
|
|
9
|
+
# sr-only) as does the caret overlay geometry + blink motion; the
|
|
10
|
+
# cell chrome (borders, active ring, invalid) rides
|
|
11
|
+
# .cn-input-otp-slot in themes/default.css.
|
|
12
|
+
class Style < Poetry::Core::Style
|
|
13
|
+
base "cn-input-otp relative flex items-center has-disabled:opacity-50"
|
|
14
|
+
|
|
15
|
+
# THE control: one real native input, full-length value, stretched
|
|
16
|
+
# invisibly over the slot row.
|
|
17
|
+
element :input, "absolute inset-0 z-20 h-full w-full opacity-[0.005] text-transparent " \
|
|
18
|
+
"caret-transparent outline-none selection:bg-transparent " \
|
|
19
|
+
"disabled:cursor-not-allowed"
|
|
20
|
+
|
|
21
|
+
element :group, "cn-input-otp-group flex items-center"
|
|
22
|
+
element :separator, "cn-input-otp-separator flex items-center"
|
|
23
|
+
|
|
24
|
+
element :slot, "cn-input-otp-slot relative flex items-center justify-center " \
|
|
25
|
+
"data-[active=true]:z-10"
|
|
26
|
+
|
|
27
|
+
# The fake-caret overlay + the blinking bar (visible only on the
|
|
28
|
+
# active EMPTY cell; steady under prefers-reduced-motion via the
|
|
29
|
+
# utilities layer).
|
|
30
|
+
element :caret, "pointer-events-none absolute inset-0 flex items-center justify-center"
|
|
31
|
+
|
|
32
|
+
element :caret_bar, "cn-input-otp-caret-line animate-caret-blink duration-1000"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= content_tag(tag, **root_attributes.to_attributes) do %>
|
|
2
|
+
<% if header? %><div data-slot="item-header" class="<%= css(:header) %>"><%= header %></div><% end %>
|
|
3
|
+
<% if media? %><div data-slot="item-media" data-variant="<%= media_variant %>" class="<%= media_classes %>"><%= media %></div><% end %>
|
|
4
|
+
<% if content_column? %>
|
|
5
|
+
<div data-slot="item-content" class="<%= css(:content) %>">
|
|
6
|
+
<% if title? %><div data-slot="item-title" class="<%= css(:title) %>"><%= title %></div><% end %>
|
|
7
|
+
<% if description? %><p data-slot="item-description" class="<%= css(:description) %>"><%= description %></p><% end %>
|
|
8
|
+
<%= content %>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if actions? %><div data-slot="item-actions" class="<%= css(:actions) %>"><%= actions %></div><% end %>
|
|
12
|
+
<% if footer? %><div data-slot="item-footer" class="<%= css(:footer) %>"><%= footer %></div><% end %>
|
|
13
|
+
<% end %>
|