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,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
# DateField family: the segmented date editor over a native input.
|
|
6
|
+
module DateField
|
|
7
|
+
# A segmented date editor: a real native <input type=date> IS the
|
|
8
|
+
# form value - with no JS the native picker works and the value is
|
|
9
|
+
# already ISO - progressively enhanced into per-segment
|
|
10
|
+
# role=spinbutton editing. The locale decides segment order and
|
|
11
|
+
# numerals; arrows cycle values, typed digits accumulate and
|
|
12
|
+
# auto-advance, and blur constrains impossible dates like
|
|
13
|
+
# February 31st. The enhanced input drops out of the tab order but
|
|
14
|
+
# keeps carrying name/required/min/max - native constraint
|
|
15
|
+
# validation stays on.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# render Poetry::Ui::DateField::Component.new(name: "event[on]", label: "Event date")
|
|
19
|
+
class Component < Poetry::Core::Component
|
|
20
|
+
include Poetry::Ui::InputGroupField
|
|
21
|
+
|
|
22
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
23
|
+
AGENT_RULES = [
|
|
24
|
+
"Date entry is a DateField (poetry_date_field / form.date_field) - never a masked " \
|
|
25
|
+
"Input, three selects, or a bare input type=date when the design system is in play.",
|
|
26
|
+
"The native input is the form value: params[<name>] is ISO (yyyy-mm-dd) with or " \
|
|
27
|
+
"without JS; min:/max: take Date or ISO strings and ride native validation.",
|
|
28
|
+
"Pair with a Label/Field for the accessible name (label for= the input id); " \
|
|
29
|
+
"standalone use takes label: - segments announce it themselves.",
|
|
30
|
+
"Locale drives segment order and numerals automatically; pass locale: only to pin " \
|
|
31
|
+
"a field to a different locale than the page."
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
# TimeField subclasses this and EXTENDS the root element with its
|
|
35
|
+
# seconds/hour-cycle values - the declarations-inheritance seam.
|
|
36
|
+
use_stimulus do
|
|
37
|
+
on :root do
|
|
38
|
+
controller :date_field do
|
|
39
|
+
register
|
|
40
|
+
value :locale, if: -> { locale.present? }
|
|
41
|
+
value :placeholder, from: :placeholder_iso
|
|
42
|
+
value :labels, from: :segment_labels_json
|
|
43
|
+
value :placeholders, from: :segment_placeholders_json
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
on :group do
|
|
47
|
+
controller :date_field do
|
|
48
|
+
target :group
|
|
49
|
+
action :focusGap, on: :click
|
|
50
|
+
action :settle, on: :focusout
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
on :input do
|
|
54
|
+
controller(:date_field) { target :input }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
option :name, :string, required: true,
|
|
59
|
+
doc: "The form field name - required; the value posts as ISO yyyy-mm-dd with or " \
|
|
60
|
+
"without JS."
|
|
61
|
+
option :value, ActiveModel::Type::Value.new, doc: "Date, or an ISO yyyy-mm-dd string; nil renders empty."
|
|
62
|
+
option :min, ActiveModel::Type::Value.new,
|
|
63
|
+
doc: "The earliest allowed date (Date or ISO string) - rides native constraint validation."
|
|
64
|
+
option :max, ActiveModel::Type::Value.new,
|
|
65
|
+
doc: "The latest allowed date (Date or ISO string) - rides native constraint validation."
|
|
66
|
+
option :required, :boolean, default: false, doc: "Marks the native input required."
|
|
67
|
+
option :disabled, :boolean, default: false, doc: "Disables the field; the segment group dims and goes inert."
|
|
68
|
+
option :readonly, :boolean, default: false, doc: "The value shows but cannot be edited."
|
|
69
|
+
option :invalid, :boolean, default: false, doc: "Paints the destructive border/ring and sets aria-invalid."
|
|
70
|
+
option :id, :string, doc: "The native input's DOM id - the Field label target."
|
|
71
|
+
option :label, :string,
|
|
72
|
+
doc: "Standalone accessible name; inside a form the Field label wires ids instead. Segments announce " \
|
|
73
|
+
"it themselves."
|
|
74
|
+
option :described_by, :string, doc: "Ids for aria-describedby (hint or error text)."
|
|
75
|
+
option :locale, :string, doc: "Pins the field to a locale other than the page's."
|
|
76
|
+
option :placeholder_value, ActiveModel::Type::Value.new,
|
|
77
|
+
doc: "What the first arrow press on an empty segment lands on; defaults to today."
|
|
78
|
+
|
|
79
|
+
part "date-field", "Root - the controller and the enhanced/disabled surface ride here",
|
|
80
|
+
states: {
|
|
81
|
+
"data-enhanced" => "the controller connected and built segments (no JS = the " \
|
|
82
|
+
"native input, visible and styled)",
|
|
83
|
+
"data-disabled" => "disabled: is set",
|
|
84
|
+
"data-invalid" => "invalid: is set (the group wears the destructive ring)"
|
|
85
|
+
}
|
|
86
|
+
# The segments themselves are controller-BUILT (the FileInput list
|
|
87
|
+
# precedent, so they are prose here, not parts): each editable
|
|
88
|
+
# segment is span[data-slot=date-field-segment] with data-type=
|
|
89
|
+
# year|month|day|hour|minute|second|dayPeriod, role=spinbutton
|
|
90
|
+
# (role=textbox on iOS, where VoiceOver cannot focus spinbuttons),
|
|
91
|
+
# and data-placeholder while unfilled; locale separators are
|
|
92
|
+
# span[data-slot=date-field-literal] aria-hidden. Those data-slot
|
|
93
|
+
# names are the restyle seam.
|
|
94
|
+
part "date-field-group", "The bordered segment row (cn-input chrome, focus-within " \
|
|
95
|
+
"ring) - hidden until enhancement, then the editing surface " \
|
|
96
|
+
"the controller fills with segments",
|
|
97
|
+
states: {
|
|
98
|
+
"data-disabled" => "disabled: is set (chrome dims, pointer events off)",
|
|
99
|
+
"data-invalid" => "invalid: is set (destructive border + ring)"
|
|
100
|
+
}
|
|
101
|
+
part "date-field-input", "The native <input type=date> - THE form value in both " \
|
|
102
|
+
"modes; tabindex -1 + aria-hidden once segments exist"
|
|
103
|
+
|
|
104
|
+
# Attributes for the field root.
|
|
105
|
+
# @api private
|
|
106
|
+
def root_attributes
|
|
107
|
+
attrs = {
|
|
108
|
+
"data-slot" => slot_prefix,
|
|
109
|
+
"class" => css
|
|
110
|
+
}.merge(component_data_attributes)
|
|
111
|
+
attrs["data-disabled"] = "" if disabled
|
|
112
|
+
attrs["data-invalid"] = "" if invalid
|
|
113
|
+
html_attributes.merge_if_not_set(attrs.merge(stimulus_attributes_for(:root)))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Attributes for the segment row the controller fills.
|
|
117
|
+
# @api private
|
|
118
|
+
def group_attributes
|
|
119
|
+
attrs = {
|
|
120
|
+
"role" => "presentation",
|
|
121
|
+
"data-slot" => "#{slot_prefix}-group",
|
|
122
|
+
"class" => group_classes
|
|
123
|
+
}
|
|
124
|
+
attrs["aria-label"] = label if label.present?
|
|
125
|
+
attrs["data-invalid"] = "" if invalid
|
|
126
|
+
attrs["data-disabled"] = "" if disabled
|
|
127
|
+
attrs.merge(stimulus_attributes_for(:group))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Attributes for the native input - the form value.
|
|
131
|
+
# @api private
|
|
132
|
+
def input_attributes
|
|
133
|
+
attrs = Poetry::Core::HTML::Attributes.new(
|
|
134
|
+
"type" => input_type,
|
|
135
|
+
"name" => name,
|
|
136
|
+
"id" => control_id,
|
|
137
|
+
"data-slot" => "#{slot_prefix}-input",
|
|
138
|
+
"class" => "#{Input::Style.css} #{css(:input)}"
|
|
139
|
+
)
|
|
140
|
+
attrs["value"] = iso(value) if value.present?
|
|
141
|
+
attrs["min"] = iso(min) if min.present?
|
|
142
|
+
attrs["max"] = iso(max) if max.present?
|
|
143
|
+
attrs["aria-label"] = label if label.present?
|
|
144
|
+
attrs["aria-invalid"] = "true" if invalid && !disabled
|
|
145
|
+
attrs["aria-describedby"] = described_by if described_by.present?
|
|
146
|
+
attrs["required"] = "" if required
|
|
147
|
+
attrs["disabled"] = "" if disabled
|
|
148
|
+
attrs["readonly"] = "" if readonly
|
|
149
|
+
attrs.merge!(stimulus_attributes_for(:input))
|
|
150
|
+
attrs
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def input_type
|
|
156
|
+
"date"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def slot_prefix
|
|
160
|
+
"date-field"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def group_classes
|
|
164
|
+
css(:group)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def iso(candidate)
|
|
168
|
+
candidate.respond_to?(:strftime) ? candidate.strftime("%F") : candidate.to_s
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def placeholder_iso
|
|
172
|
+
placeholder_value.present? ? iso(placeholder_value) : Date.current.strftime("%F")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# The AT-facing strings (segment-name fallbacks + the Empty
|
|
176
|
+
# valuetext) and the visual placeholder text per segment - the
|
|
177
|
+
# only translated surface; dates localize themselves via Intl.
|
|
178
|
+
def segment_labels
|
|
179
|
+
{
|
|
180
|
+
empty: t("poetry.date_field.empty"),
|
|
181
|
+
year: t("poetry.date_field.year"),
|
|
182
|
+
month: t("poetry.date_field.month"),
|
|
183
|
+
day: t("poetry.date_field.day"),
|
|
184
|
+
hour: t("poetry.date_field.hour"),
|
|
185
|
+
minute: t("poetry.date_field.minute"),
|
|
186
|
+
second: t("poetry.date_field.second"),
|
|
187
|
+
dayPeriod: t("poetry.date_field.day_period")
|
|
188
|
+
}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def segment_placeholders
|
|
192
|
+
{
|
|
193
|
+
year: t("poetry.date_field.placeholder_year"),
|
|
194
|
+
month: t("poetry.date_field.placeholder_month"),
|
|
195
|
+
day: t("poetry.date_field.placeholder_day"),
|
|
196
|
+
hour: t("poetry.date_field.placeholder_time"),
|
|
197
|
+
minute: t("poetry.date_field.placeholder_time"),
|
|
198
|
+
second: t("poetry.date_field.placeholder_time")
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def segment_labels_json = segment_labels.to_json
|
|
203
|
+
def segment_placeholders_json = segment_placeholders.to_json
|
|
204
|
+
|
|
205
|
+
private :root_attributes, :group_attributes, :input_attributes
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DateField
|
|
6
|
+
class Preview < Poetry::Core::Preview::Base
|
|
7
|
+
# Empty: placeholder segments (mm/dd/yyyy under en) once enhanced;
|
|
8
|
+
# the styled native input before that.
|
|
9
|
+
def default
|
|
10
|
+
render_component(name: "event[on]", label: "Event date")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# A server value fills the segments; the native input carries ISO.
|
|
14
|
+
def with_value
|
|
15
|
+
render_component(name: "invoice[due_on]", value: Date.new(2026, 3, 9), label: "Due date")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def with_range
|
|
19
|
+
render_component(name: "booking[night]", label: "First night",
|
|
20
|
+
min: Date.new(2026, 1, 1), max: Date.new(2026, 12, 31))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def invalid
|
|
24
|
+
render_component(name: "event[on]", label: "Event date", invalid: true,
|
|
25
|
+
value: Date.new(2020, 2, 29))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def disabled
|
|
29
|
+
render_component(name: "event[on]", label: "Event date", disabled: true,
|
|
30
|
+
value: Date.new(2026, 7, 13))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# locale: pins segment order and placeholders (dd.mm.yyyy under
|
|
34
|
+
# de-DE) instead of following the page locale.
|
|
35
|
+
def localized
|
|
36
|
+
render_component(name: "geburt[am]", label: "Geburtsdatum", locale: "de-DE",
|
|
37
|
+
value: Date.new(2026, 3, 9))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DateField
|
|
6
|
+
# Utility-only (the Separator/Spinner rule): the GROUP wears
|
|
7
|
+
# cn-input's field chrome (zero new theme CSS) with a focus-within
|
|
8
|
+
# ring standing in for the input's focus-visible one, since focus
|
|
9
|
+
# lives on the segments inside. Segments and literals are
|
|
10
|
+
# controller-BUILT, so their look rides descendant variants on the
|
|
11
|
+
# group (the FileInput list idiom). The group stays hidden until the
|
|
12
|
+
# controller stamps data-enhanced on the root; until then the native
|
|
13
|
+
# input is the visible, styled control (no-JS = native pickers).
|
|
14
|
+
class Style < Poetry::Core::Style
|
|
15
|
+
base "group/date-field flex w-full flex-col"
|
|
16
|
+
|
|
17
|
+
element :group,
|
|
18
|
+
"cn-input hidden w-fit min-w-0 select-none items-center tabular-nums " \
|
|
19
|
+
"group-data-[enhanced]/date-field:flex " \
|
|
20
|
+
"focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 " \
|
|
21
|
+
"data-invalid:border-destructive data-invalid:ring-destructive/20 " \
|
|
22
|
+
"dark:data-invalid:ring-destructive/40 " \
|
|
23
|
+
"data-disabled:pointer-events-none data-disabled:opacity-50 " \
|
|
24
|
+
"[&_[data-slot=date-field-segment]]:rounded-sm " \
|
|
25
|
+
"[&_[data-slot=date-field-segment]]:px-0.5 " \
|
|
26
|
+
"[&_[data-slot=date-field-segment]]:outline-none " \
|
|
27
|
+
"[&_[data-slot=date-field-segment]:focus]:bg-accent " \
|
|
28
|
+
"[&_[data-slot=date-field-segment]:focus]:text-accent-foreground " \
|
|
29
|
+
"[&_[data-slot=date-field-segment][data-placeholder]]:text-muted-foreground " \
|
|
30
|
+
"[&_[data-slot=date-field-literal]]:text-muted-foreground"
|
|
31
|
+
element :input, "group-data-[enhanced]/date-field:sr-only"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%= content_tag(:div, **root_attributes.to_attributes) do %>
|
|
2
|
+
<% if input_variant? %>
|
|
3
|
+
<%= render Poetry::Ui::InputGroup::Component.new do %>
|
|
4
|
+
<%= helpers.poetry_input_group_input(**input_attributes.symbolize_keys) %>
|
|
5
|
+
<%= helpers.poetry_input_group_addon(align: :"inline-end") do %>
|
|
6
|
+
<%= render Poetry::Ui::Popover::Component.new(**popover_options, align: :end) do |popover| %>
|
|
7
|
+
<% popover.with_trigger(**input_trigger_options) do %>
|
|
8
|
+
<%= render Poetry::Ui::Icon::Component.new(name: :calendar) %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<%= render Poetry::Ui::Calendar::Component.new(**calendar_options) %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% else %>
|
|
15
|
+
<%= render Poetry::Ui::Popover::Component.new(**popover_options) do |popover| %>
|
|
16
|
+
<% popover.with_trigger(**trigger_options) do %>
|
|
17
|
+
<%= render Poetry::Ui::Icon::Component.new(name: :calendar) %>
|
|
18
|
+
<span class="min-w-0 truncate" <%= tag.attributes(**label_target_attributes) %>><%= formatted %></span>
|
|
19
|
+
<% end %>
|
|
20
|
+
<%= render Poetry::Ui::Calendar::Component.new(**calendar_options) %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Poetry
|
|
6
|
+
module Ui
|
|
7
|
+
# DatePicker family: a field-shaped trigger opening a calendar popover.
|
|
8
|
+
module DatePicker
|
|
9
|
+
# A date picker: a field-shaped trigger that opens a Calendar in a
|
|
10
|
+
# Popover. Composition, not a new primitive: the Popover owns the
|
|
11
|
+
# overlay, the Calendar owns selection and the form value (name:
|
|
12
|
+
# posts the chosen date as ISO through its hidden input), and a
|
|
13
|
+
# thin glue layer formats the trigger label and closes on pick. A
|
|
14
|
+
# preselected value: renders the formatted label and the chosen day
|
|
15
|
+
# with no JS.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# render Poetry::Ui::DatePicker::Component.new(
|
|
19
|
+
# name: "due_on", label: "Due date", value: Date.new(2026, 6, 5)
|
|
20
|
+
# )
|
|
21
|
+
class Component < Poetry::Core::Component
|
|
22
|
+
# Composition: every DatePicker renders a Popover, whose minted
|
|
23
|
+
# trigger/content ids ride this DOM - a cached or looped
|
|
24
|
+
# DatePicker without identity freezes them, so the stable-identity
|
|
25
|
+
# warning is the point. Forces the registry identity fact past
|
|
26
|
+
# the source derivation (no funnel call in this family itself);
|
|
27
|
+
# popover_options forwards key:/id: to that Popover, so the
|
|
28
|
+
# warning's remedy works through the composition.
|
|
29
|
+
IDENTITY = true
|
|
30
|
+
|
|
31
|
+
# Projected into the registry, llms.txt, and the agent surface.
|
|
32
|
+
AGENT_RULES = [
|
|
33
|
+
"name: is REQUIRED - the chosen date posts as an ISO string (the Calendar's hidden input).",
|
|
34
|
+
"value: preselects a date (a Date or ISO string) - the trigger shows it formatted, no JS needed.",
|
|
35
|
+
"min:/max: bound the selectable range; the label + placeholder are the trigger's text.",
|
|
36
|
+
"key:/id: forwards to the composed Popover - a keyed DatePicker renders cache-stable popover ids.",
|
|
37
|
+
"variant: :input renders a text field with a calendar button - typed parseable dates " \
|
|
38
|
+
"re-select the calendar; single mode only.",
|
|
39
|
+
"For an always-visible grid use Calendar directly - DatePicker is the field+popover form."
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
use_stimulus do
|
|
43
|
+
on :root do
|
|
44
|
+
controller :date_picker do
|
|
45
|
+
register
|
|
46
|
+
value :placeholder
|
|
47
|
+
value :mode, "range", if: :range?
|
|
48
|
+
action :picked, on: event(:calendar, :change)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
on :label do
|
|
52
|
+
controller(:date_picker) { target :label }
|
|
53
|
+
end
|
|
54
|
+
# The input variant's text field: the picker's input target plus
|
|
55
|
+
# the typed-date sync and ArrowDown-opens actions.
|
|
56
|
+
on :input do
|
|
57
|
+
controller :date_picker do
|
|
58
|
+
target :input
|
|
59
|
+
action :input_changed, on: :input
|
|
60
|
+
action :input_keydown, on: :keydown
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
option :name, :string, required: true, doc: "The form field name - required; the chosen date posts as ISO."
|
|
66
|
+
option :mode, :symbol, default: :single,
|
|
67
|
+
doc: ":single or :range (two-date selection; the trigger shows the joined pair)."
|
|
68
|
+
option :placeholder, :string, default: "Pick a date", doc: "Trigger text while nothing is chosen."
|
|
69
|
+
option :label, :string,
|
|
70
|
+
doc: "The trigger's accessible name (aria-label)."
|
|
71
|
+
option :variant, :symbol, default: :button,
|
|
72
|
+
doc: ":button (the default trigger) or :input - a text field that accepts a typed " \
|
|
73
|
+
"date (parseable text re-selects the calendar) with a calendar icon-button " \
|
|
74
|
+
"opening the popover. Single mode only."
|
|
75
|
+
option :caption_layout, :symbol, default: :label,
|
|
76
|
+
doc: "Forwarded to the wrapped Calendar: :dropdown swaps the caption for " \
|
|
77
|
+
"month + year selects (the date-of-birth recipe - min:/max: bound the " \
|
|
78
|
+
"year list)."
|
|
79
|
+
|
|
80
|
+
# ONE owned part: DatePicker is composition - the Popover owns the
|
|
81
|
+
# overlay, the Calendar owns the grid + the form value, the trigger
|
|
82
|
+
# Button owns data-slot=date-picker-trigger (each under its own
|
|
83
|
+
# data-component root, so each declares its own contract).
|
|
84
|
+
part "date-picker", "Root wrapper - the glue controller (formats the trigger label, " \
|
|
85
|
+
"closes on pick) around the composed Popover + Calendar"
|
|
86
|
+
|
|
87
|
+
# Parses the date collaborators (value/min/max/month) up front.
|
|
88
|
+
# @api private
|
|
89
|
+
def initialize(value: nil, min: nil, max: nil, month: nil, **)
|
|
90
|
+
super(**)
|
|
91
|
+
if range?
|
|
92
|
+
@range_start, @range_end = parse_range(value)
|
|
93
|
+
else
|
|
94
|
+
@value = to_date(value)
|
|
95
|
+
end
|
|
96
|
+
@min = to_date(min)
|
|
97
|
+
@max = to_date(max)
|
|
98
|
+
@month = to_date(month)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Enforces the name/variant/mode contracts.
|
|
102
|
+
# @api private
|
|
103
|
+
def before_render
|
|
104
|
+
raise ArgumentError, "DatePicker requires name: (the form field)" if name.blank?
|
|
105
|
+
raise ArgumentError, "DatePicker variant: must be :button or :input" unless %i[button input].include?(variant)
|
|
106
|
+
raise ArgumentError, "DatePicker variant: :input is single-mode only" if input_variant? && range?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The parsed date collaborators for the template.
|
|
110
|
+
# @api private
|
|
111
|
+
attr_reader :value, :min, :max, :month, :range_start, :range_end
|
|
112
|
+
|
|
113
|
+
# Whether mode: is :range.
|
|
114
|
+
# @api private
|
|
115
|
+
def range? = mode == :range
|
|
116
|
+
|
|
117
|
+
# Whether variant: is :input.
|
|
118
|
+
# @api private
|
|
119
|
+
def input_variant? = variant == :input
|
|
120
|
+
|
|
121
|
+
# Range mode joins the pair with SHORT month names ("Jun 5, 2026 -
|
|
122
|
+
# Jun 12, 2026" - two long-month dates outgrow any reasonable
|
|
123
|
+
# trigger); a start-only value shows one date.
|
|
124
|
+
# @api private
|
|
125
|
+
def formatted
|
|
126
|
+
if range?
|
|
127
|
+
return placeholder unless @range_start
|
|
128
|
+
|
|
129
|
+
[@range_start, @range_end].compact.map { |date| date.strftime("%b %-d, %Y") }.join(" – ")
|
|
130
|
+
else
|
|
131
|
+
@value ? @value.strftime("%B %-d, %Y") : placeholder
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Options forwarded to the wrapped Calendar.
|
|
136
|
+
# @api private
|
|
137
|
+
def calendar_options
|
|
138
|
+
selected = range? ? [@range_start, @range_end].compact.presence : @value
|
|
139
|
+
{ name: name, mode: (:range if range?), selected: selected,
|
|
140
|
+
caption_layout: (caption_layout unless caption_layout == :label),
|
|
141
|
+
min: @min, max: @max, month: @month || @value || @range_start }.compact
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Attributes for the root wrapper.
|
|
145
|
+
# @api private
|
|
146
|
+
def root_attributes
|
|
147
|
+
html_attributes.merge_if_not_set(
|
|
148
|
+
{ "data-slot" => "date-picker", "class" => css }
|
|
149
|
+
.merge(stimulus_attributes_for(:root)).merge(component_data_attributes)
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Button options for the field-shaped trigger.
|
|
154
|
+
# @api private
|
|
155
|
+
def trigger_options
|
|
156
|
+
{
|
|
157
|
+
variant: :outline,
|
|
158
|
+
label: label.presence,
|
|
159
|
+
class: css(range? ? :trigger_range : :trigger_single,
|
|
160
|
+
class: [css(:trigger_chrome),
|
|
161
|
+
(css(:trigger_placeholder) unless value)].compact.join(" ")),
|
|
162
|
+
data: { slot: "date-picker-trigger" }
|
|
163
|
+
}.compact
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Wiring for the trigger-label span the controller rewrites.
|
|
167
|
+
# @api private
|
|
168
|
+
def label_target_attributes
|
|
169
|
+
stimulus_attributes_for(:label)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# The input variant's text field: the picker's input target plus the
|
|
173
|
+
# typed-date sync and ArrowDown-opens actions. It carries NO name -
|
|
174
|
+
# the calendar's hidden ISO input stays THE form value.
|
|
175
|
+
# @api private
|
|
176
|
+
def input_attributes
|
|
177
|
+
stimulus_attributes_for(:input).merge(
|
|
178
|
+
"value" => (formatted if value), "placeholder" => placeholder,
|
|
179
|
+
"aria-label" => label.presence || "Date"
|
|
180
|
+
).compact
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The addon's icon button IS the popover trigger - InputGroup's own
|
|
184
|
+
# icon-xs chrome on Popover's wired Button.
|
|
185
|
+
# @api private
|
|
186
|
+
def input_trigger_options
|
|
187
|
+
style = Poetry::Ui::InputGroup::Style
|
|
188
|
+
{
|
|
189
|
+
variant: :ghost, label: label.presence || "Choose date",
|
|
190
|
+
class: [style.css(:button), style.css(:button_icon_xs)].join(" "),
|
|
191
|
+
"data-size": "icon-xs", data: { slot: "date-picker-trigger" }
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Options for the composed Popover. Identity forwards: a keyed
|
|
196
|
+
# (or explicitly-id'd) DatePicker keys the Popover too, so its
|
|
197
|
+
# minted trigger/content ids are render-stable - the
|
|
198
|
+
# stable-identity warning's remedy works through the composition.
|
|
199
|
+
# @api private
|
|
200
|
+
def popover_options
|
|
201
|
+
{ label: label.presence || "Choose date", content_class: "w-auto p-0",
|
|
202
|
+
key: popover_key }.compact
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# The forwarded identity: the caller's key:, or the explicit root
|
|
206
|
+
# id (render-stable already; the Popover namespaces its own ids
|
|
207
|
+
# from it, so no duplicate).
|
|
208
|
+
# @api private
|
|
209
|
+
def popover_key
|
|
210
|
+
stable_key || html_attributes["id"].presence
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
private
|
|
214
|
+
|
|
215
|
+
def to_date(value)
|
|
216
|
+
return if value.nil?
|
|
217
|
+
return value if value.is_a?(Date)
|
|
218
|
+
|
|
219
|
+
Date.parse(value.to_s)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# A preselected range: Date..Date, [start, end], or {start:, end:}.
|
|
223
|
+
def parse_range(value)
|
|
224
|
+
case value
|
|
225
|
+
when nil then [nil, nil]
|
|
226
|
+
when Range then [to_date(value.first), to_date(value.last)]
|
|
227
|
+
when Array then [to_date(value[0]), to_date(value[1])]
|
|
228
|
+
when Hash
|
|
229
|
+
pair = value.symbolize_keys
|
|
230
|
+
[to_date(pair[:start]), to_date(pair[:end])]
|
|
231
|
+
else
|
|
232
|
+
[to_date(value), nil]
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private :range?, :input_variant?, :formatted, :calendar_options, :root_attributes, :trigger_options
|
|
237
|
+
private :label_target_attributes, :input_attributes, :input_trigger_options
|
|
238
|
+
private :popover_options, :popover_key
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DatePicker
|
|
6
|
+
# The DatePicker preview: the empty (placeholder) trigger and a
|
|
7
|
+
# preselected one (server-formatted, no JS).
|
|
8
|
+
class Preview < Poetry::Core::Preview::Base
|
|
9
|
+
def default
|
|
10
|
+
render_component(name: "due_on", label: "Due date")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def preselected
|
|
14
|
+
render_component(name: "due_on", label: "Due date", value: "2026-06-12", month: "2026-06-01")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def range
|
|
18
|
+
render_component(name: "stay", mode: :range, label: "Stay dates",
|
|
19
|
+
placeholder: "Pick a date range",
|
|
20
|
+
value: %w[2026-06-09 2026-06-18], month: "2026-06-01")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def input
|
|
24
|
+
render_component(name: "subscription_on", variant: :input, label: "Subscription date",
|
|
25
|
+
placeholder: "June 1, 2026", value: "2026-06-01", month: "2026-06-01",
|
|
26
|
+
class: "w-64")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Ui
|
|
5
|
+
module DatePicker
|
|
6
|
+
# The DatePicker is composition (Popover + Calendar); it owns no
|
|
7
|
+
# visual of its own beyond the trigger's field shape, which the
|
|
8
|
+
# component sets inline on the Button. This dictionary exists so the
|
|
9
|
+
# component participates in the registry/verify machinery; the root is
|
|
10
|
+
# an inline-block wrapper.
|
|
11
|
+
class Style < Poetry::Core::Style
|
|
12
|
+
base "inline-block"
|
|
13
|
+
|
|
14
|
+
# The trigger Button's classes (dictionary-resident so safelists
|
|
15
|
+
# ship them - a bare Ruby string compiles in no host). Range
|
|
16
|
+
# triggers run wider (two dates + the dash; upstream sizes its
|
|
17
|
+
# range demo up the same way).
|
|
18
|
+
element :trigger_single, "w-56"
|
|
19
|
+
element :trigger_range, "w-72"
|
|
20
|
+
element :trigger_chrome, "justify-start font-normal"
|
|
21
|
+
element :trigger_placeholder, "text-muted-foreground"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%= content_tag(:"turbo-frame", **root_attributes.to_attributes) do %>
|
|
2
|
+
<div <%= tag.attributes(**stimulus_attributes_for(:placeholder)) %>>
|
|
3
|
+
<%= content.presence || render(Poetry::Ui::Skeleton::Component.new(class: "h-24 w-full")) %>
|
|
4
|
+
</div>
|
|
5
|
+
<template <%= tag.attributes(**stimulus_attributes_for(:error_template)) %>>
|
|
6
|
+
<div data-slot="deferred-error" class="flex flex-col items-start gap-2">
|
|
7
|
+
<p class="text-sm text-destructive"><%= t("poetry.deferred.failed") %></p>
|
|
8
|
+
<%= render(Poetry::Ui::Button::Component.new(
|
|
9
|
+
variant: :outline, size: :sm,
|
|
10
|
+
**stimulus_attributes_for(:retry)
|
|
11
|
+
)) do %>
|
|
12
|
+
<%= t("poetry.deferred.retry") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
<% end %>
|